freeswitch的event事件处理

概述


之前的文章中,我们讲解了freeswitch的源码基本结构,如何新增一个插件式模块,以及如何在模块中新增一个命令式API接口和APP接口。


freeswitch本身是事件驱动的,它可以并发响应多个事件,也可以广播事件。


freeswitch的事件可以由核心产生,也可以由外部模块或外部源产生。


freeswitch系统中的几乎所有事件都会产生事件消息,这些事件可以被外部实体监听(通过event socket),也可以被内部模块监听。


freeswitch的事件系统是双向的,除了允许外部程序监听事件外,外部程序还可以向freeswitch发送事件。


你可以从自己的程序中实时发送/接收事件。这种组合允许你以几乎任何你能想到的方式使用freeswitch。

通道事件


在freeswitch的事件系统中,有一类以“CHANNEL_“开头的事件,这些事件表示了一个呼叫通道(channel)的状态变化的全部过程,是我们在业务开发中最常用的一类事件。


常见的通道事件:

CHANNEL_ANSWER
CHANNEL_APPLICATION
CHANNEL_BRIDGE
CHANNEL_CALLSTATE
CHANNEL_CREATE
CHANNEL_DATA
CHANNEL_DESTROY
CHANNEL_EXECUTE
CHANNEL_EXECUTE_COMPLETE
CHANNEL_GLOBAL
CHANNEL_HANGUP
CHANNEL_HANGUP_COMPLETE
CHANNEL_HOLD
CHANNEL_ORIGINATE
CHANNEL_OUTGOING
CHANNEL_PARK
CHANNEL_PROGRESS
CHANNEL_PROGRESS_MEDIA
CHANNEL_STATE
CHANNEL_UNBRIDGE
CHANNEL_UNHOLD
CHANNEL_UNPARK
CHANNEL_UUID

通道事件可以携带一通呼叫的全部呼叫信息,也可以携带呼叫流程中的自定义信息,这个属性让我们可以很方便的在一通呼叫的不同阶段之间传递自定义参数。


本节我们来介绍如何在模块中增加一个channel event事件处理,并传递一个自定义参数。 

开发环境


centos:CentOS release 7.0 (Final)或以上版本


freeswitch:v1.8.7


GCC:4.8.5

代码处理


新增模块的方法请参考之前的内容,本节内容在模块mod_task的基础上修改。

mod_task.c内容如下:

#include <switch.h>

SWITCH_MODULE_LOAD_FUNCTION(mod_task_load);
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_task_shutdown);
SWITCH_MODULE_DEFINITION(mod_task, mod_task_load, mod_task_shutdown, NULL);

SWITCH_STANDARD_API(task_api_function)
{
    //SWITCH_STANDARD_API have three args: (cmd, session, stream)
    char *mycmd = NULL;
    int argc = 0;
    char *argv[16];
    bzero(argv, sizeof(argv));

    //split cmd and parse
    if (cmd)
    {
        mycmd = strdup(cmd);
        if (!mycmd)
        {
            stream->write_function(stream, "Out of memory\n");
            return SWITCH_STATUS_FALSE;    
        }
        
        if (!(argc = switch_split(mycmd, ' ', argv)) || !argv[0]) 
        {
            argc = 0;
            switch_safe_free(mycmd);
            return SWITCH_STATUS_FALSE;
        }
    }

    //parse cmd, brach process
    if(0 == strcmp("test1", argv[0]))
    {
        stream->write_function(stream, "task api test1, cmd:%s, session:%p", cmd, session);
    }
    else if(0 == strcmp("test2", argv[0]))
    {
        stream->write_function(stream, "task api test2, cmd:%s, session:%p", cmd, session);
    }
    else
    {
        stream->write_function(stream, "unknown cmd, cmd:%s, session:%p", cmd, session);
    }    

    switch_safe_free(mycmd);
    return SWITCH_STATUS_SUCCESS;
}

SWITCH_STANDARD_APP(task_app_function)
{
    switch_channel_t *pchannel = NULL;

    //task_app(session, data);
    switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, 
            "task_app_function start, session=%p, data=%s\n", (void*)session, data);

    //export variable task_str for hangup event
    pchannel = switch_core_session_get_channel(session);
    if(NULL != pchannel)
    {
        switch_channel_export_variable(pchannel, "task_str", "task_app export variable", SWITCH_EXPORT_VARS_VARIABLE);
    }
}

void task_event_channel_hangup_complete(switch_event_t *event)
{
    const char *uuid = switch_event_get_header(event, "Unique-ID");
    const char *call_dir = switch_event_get_header(event, "Call-Direction");
    const char* task_str = switch_event_get_header(event, "variable_task_str");

    switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, 
        "task_event_channel_hangup_complete, uuid=%s, call_dir=%s, task_str=%s\n", 
        uuid, call_dir, task_str);
}

void task_event_handler(switch_event_t *event)
{
    switch (event->event_id)
    {
        case SWITCH_EVENT_CHANNEL_HANGUP_COMPLETE:
            task_event_channel_hangup_complete(event);
            break;

        case SWITCH_EVENT_CHANNEL_ANSWER:
        default:
            switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, 
                "unsupported event. event:%d\n", event->event_id); 
            break;
    }

    return;
}

SWITCH_MODULE_LOAD_FUNCTION(mod_task_load)
{
    switch_api_interface_t* api_interface = NULL;
    switch_application_interface_t* app_interface = NULL;
    *module_interface = switch_loadable_module_create_module_interface(pool, modname);

    switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, 
            "mod_task_load start\n");

    // register APP
    SWITCH_ADD_APP(app_interface,
        "task_app", 
        "task_app", 
        "task_app", 
        task_app_function, 
        "NULL", 
        SAF_SUPPORT_NOMEDIA | SAF_ROUTING_EXEC);

    // register API
    SWITCH_ADD_API( api_interface, 
                    "task", 
                    "task api", 
                    task_api_function, 
                    "<cmd> <args>");
    
    // 注册终端命令自动补全 
    switch_console_set_complete("add task test1 [args]");
    switch_console_set_complete("add task test2 [args]");

    ///EVENT INIT
    if (SWITCH_STATUS_SUCCESS != switch_event_bind(modname, SWITCH_EVENT_CHANNEL_HANGUP_COMPLETE, SWITCH_EVENT_SUBCLASS_ANY, task_event_handler, NULL)){
        switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "can't bind event\n");
        return SWITCH_STATUS_GENERR;
    }

    return SWITCH_STATUS_SUCCESS;
}

SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_task_shutdown)
{
    switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, 
            "mod_task_shutdown stop\n");
    return SWITCH_STATUS_SUCCESS;
}

呼叫处理过程中,我们在“task_app_function“函数中通过”switch_channel_export_variable“接口设置了一个自定义通道变量”task_str“的值为”task_app export variable“。


然后在呼叫的挂机事件中,我们又取出了消息头域中“variable_task_str“的值并打印到日志中。


通过这种方式,我们可以在呼叫流程的不同阶段传递任意自定义参数

编译安装


进入task模块目录,编译安装,在Makefile.am文件未变化的情况下,不需要重新config。

cd  $(top_srcdir)/src/mod/applications/mod_task
make  install

配置启动


修改dialplan拨号计划

cd /usr/local/freeswitch/conf/dialplan
vi public.xml
…
<include>
  <context name="public">
    <extension name="test">
      <condition>
        <action application="task_app" data="${destination_number}"/>
      </condition>
    </extension>
…

启动fs

cd /usr/local/freeswitch/bin/
./freeswitch –nonat

加载测试


freeswitch启动成功后,在freeswitch命令行中输入API命令加载mod_task模块:

freeswitch@localhost.localdomain> load mod_task
2021-09-03 11:34:50.954223 [INFO] mod_enum.c:882 ENUM Reloaded
2021-09-03 11:34:50.954223 [INFO] mod_task.c:134 mod_task_load start
2021-09-03 11:34:50.954223 [CONSOLE] switch_loadable_module.c:1540 Successfully Loaded [mod_task]
2021-09-03 11:34:50.954223 [NOTICE] switch_loadable_module.c:292 Adding Application 'task_app'

+OK Reloading XML
+OK

2021-09-03 11:34:50.954223 [NOTICE] switch_loadable_module.c:338 Adding API Function 'task'

通过其他sip服务器发起invite呼叫到本机的5080端口,在日志中可以查看到:

freeswitch@localhost.localdomain> 2021-09-03 11:34:56.614251 [NOTICE] switch_channel.c:1114 New Channel sofia/external/10011@192.168.0.110 [a34a67c3-2b8d-401f-a16f-1f5ec3e5169f]
2021-09-03 11:34:56.614251 [INFO] mod_dialplan_xml.c:637 Processing 10011 <10011>->10012 in context public
2021-09-03 11:34:56.614251 [INFO] mod_task.c:88 task_app_function start, session=0x7fbb8402fab8, data=10012
2021-09-03 11:34:56.614251 [NOTICE] switch_core_state_machine.c:385 sofia/external/10011@192.168.0.110 has executed the last dialplan instruction, hanging up.
2021-09-03 11:34:56.614251 [NOTICE] switch_core_state_machine.c:387 Hangup sofia/external/10011@192.168.0.110 [CS_EXECUTE] [NORMAL_CLEARING]
2021-09-03 11:34:56.614251 [INFO] mod_task.c:105 task_event_channel_hangup_complete, uuid=a34a67c3-2b8d-401f-a16f-1f5ec3e5169f, call_dir=inbound, task_str=task_app export variable
2021-09-03 11:34:56.614251 [NOTICE] switch_core_session.c:1744 Session 1 (sofia/external/10011@192.168.0.110) Ended
2021-09-03 11:34:56.614251 [NOTICE] switch_core_session.c:1748 Close Channel sofia/external/10011@192.168.0.110 [CS_DESTROY]


在日志中,可以看到“task_app_function start “的信息,同时也可以看到” task_event_channel_hangup_complete “的函数打印中”task_str=task_app export variable “的打印信息,验证了在呼叫中设置的自定义参数传递到挂机事件的后处理的过程

总结


freeswitch的event事件是整个架构体系中非常重要的一环,基础核心层通过event事件将所有呼叫相关的信息异步的通知到应用层,极大的方便了呼叫流程的业务开发。


其中的异步设计思想值得我们多多参考学习。


空空如常

求真得真
 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值