FreeSwitch B2B 状态转换流程
以1002 呼叫 1003 为例。
Sip的状态流程
1002向FS发起INVITE消息,经过一次认证通过,1002在此向FS发起INIVTE消息。此时FS所做的处理为:1. 捕获该sip消息,在sofia中有一个对于ua Event消息事件的枚举定义:
typedef enum nua_event_e {
……….
nua_i_invite;//这个值表明回调sofia_event_callback时,是一个invite事件到来。
………
nua_i_state;//这个值表明回调sofia_event_callback时,Call state 发生了变化。
}
所谓的Callstate,就是服务器对sip协议进展过程的状态变换。
例如 1002---à1003时,FreeSwitch的sip协议栈会按照如下流程进行sip状态转化:
① 收到INVITE后,向1002发送100Trying,此时,在sofia提供的状态转化机中,会产生
nua_i_state 的event,因此根据此event回调sofia_event_callback(…)函数,并根据相应的event事件进入对应的事件处理函数,这里进入的事件处理函数是sofia_handle_sip_i_state(…)。而表示消息event的序号是100,m描述状态字符串phrase是”100 Trying”。进入处理函数后,FS会根据state判断进入相应的处理流程,这里,将会进入到nua_callstate_recveied的处理流程。
② 当FreeSwitch向1003发送INVITE同样会引发event回调,并传回一个相应的state,需要弄清楚的是:FreeSwitch是怎样进行内部处理的。
③ 当1003 最终的”200ok” 和1002 最终的”Ack”抵达时,FreeSwitch会根据回调的state进入sofia_handle_sip_i_state(…)nua_callstate_ready的处理流程,意味着双方都已最好准备,可以通话了。
④ 需要注意的两点,一是在sofia_event_callback中是根据event类型选择处理函数,而event类型为nua_i_state时进入sofia_handle_sip_i_state(…)处理callstate状态变化事件。二是对于call state的描述同样是一个枚举类型,定义为:
enum nua_callstate {
nua_callstate_init, /**<Initial state */
nua_callstate_authenticating, /**< 401/407 received */
nua_callstate_calling, /**< INVITE sent*/
nua_callstate_proceeding, /**< 18X received */
nua_callstate_completing, /**< 2XXreceived */
nua_callstate_received, /**< INVITEreceived */
nua_callstate_early, /**< 18Xsent (w/SDP) */
nua_callstate_completed, /**< 2XX sent*/
nua_callstate_ready, /**< 2XX received, ACK sent, or vice versa */
nua_callstate_terminating, /**< BYE sent */
nua_callstate_terminated /**< BYEcomplete */
};
所以,sofia_handle_sip_i_state(…)函数将会根据此枚举类型和传入的status,phrase(函数形参)来选择不同的处理流程。
B2B Channel的状态流程
INVITE达到sofia模块的处理
Channel在FreeSwitch中描述了一个UA与US之间的数据传送通道。也就是说,channel更多的概念是与协议无关的,而是与数据传送有关。因此,channel的状态变化不可能是与协议状态变化完全一致的,但是也不可能脱离协议状态而存在。当FS接收到来自1002的INVITE消息后,经过407认证,会立即为1002建立一个session,一个类型为switch_core_session_t的实例。session是一个对于会话的抽象,这个session负责了资源管理的较色,当给1002创建了一个session以后,就意味着为1002与FreeSwitch之间的联系分配了资源,也就是为session创建了一个资源池,以后与1002有关的东西,都会在该资源池中分配,包括Freeswitch与1002之间的数据通道channel实例。
对于session而言,它还包括了一个名字为private_info的void*指针,这个指针是为不同的协议终端创建的,它用到的所有资源包括自身结构的内存区都在session的资源池中创建,对于基于sip协议的sofia终端来说,private_info指向一个private_object_t 类型的实例,在sofia_handle_sip_i_invite(…)函数中创建的,最终完成该private_object_t实例的各项字段的设置后,将用sofia_glue_attach_private(…)函数与session挂接起来,最终使用的核心函数
switch_core_session_set_private(session, tech_pvt),tech_pvt便是指向该实例的指针。同时还要为该私有实例设置codec信息:sofia_glue_tech_prepare_codecs(tech_pvt)。
在sofai_handle_sip_i_invite(…)函数中,还需要完成的一件工作就是完成针对于channel的switch_caller_profile_t实例的构建,具体的调用语句为:
tech_pvt->caller_profile =switch_caller_profile_new ( switch_core_session_get_pool(session),
from_user,dialplan,displayname,from_user, network_ip, from_user, aniii, NULL, MODNAME, context,destination_number)
可见,switch_core_session_get_pool(session)用于获取session的资源池,在其他非核心模块中,是不包含switch_core_pvt.h头文件的,而该头文件包含了switch_core_session结构体的具体定义,在其他模块中,只包含了switch_type.h头文件,因此在编译时,只知道session是一个switch_core_session_t类型的指针,而并不知道结构体的具体含义,只有将指针指向的地址传给核心函数,在核心模块包含了switch_core_pvt.h头文件的文件中才能对session具体字段进行操作。这就很好地对session进行了数据封装了,而核心组件则会提供给其余模块操作session的借口,对于很多结构体,都是如此的处理。
而且从函数的实参dialplan可以看出,呼叫路由是保存在caller_profile中得,在实际的调用中,到变量的值为”XML”,所以在今后的呼叫路由查找时,回去读取dialplan目录下得配置文件,进行路由查询,以获取相应的switch_caller_extenion_t实例,在switch_caller_extension_t实例中,有一个switch_caller_application_t的应用程序结构实例链,表示了该呼叫需要执行的应用程序,具体的执行,将在数据通道channel状态为CS_EXCUTE时依次执行。
最终,sofia_handle_sip_i_inivte会设置channel的状态为CS_NEW,并启动channel的状态转换线程:
switch_core_session_thread_launch(session)--à switch_core_session_thread(trhead,obj)-à
switch_core_session_run(session)
于是进入session的数据通道channel的状态转化机。
STATE_MACRO宏
这是一个针对于不同channel状态的标准处理流程,因为其标准,故对于需要做如此处理的channel状态来说,定义一个宏是比较简单的。例如对于channel在CS_ROUTING时,其宏为STATE_MACRO(routing,"ROUTING"),展开后完成几个工作:
1. if(driver_state_handler->on_routing)
driver_state_handler->on_routing(session)
即如果driver_state_handler->on_routing处理函数有定义,则会调用driver_state_handler的on_routing处理函数。这里的driver_state_handler是根据不同endpoin_interface定义的channel状态处理函数表,FS将endpoint视为了driver。对于sofia的sip endpoint来说,该函数处理表的定义为:
switch_state_handler_table_tsofia_event_handlers = {
/*.on_init */ sofia_on_init,
/*.on_routing */ sofia_on_routing,
/*.on_execute */ sofia_on_execute,
/*.on_hangup */ sofia_on_hangup,
/*.on_exchange_media */ sofia_on_exchange_media,
/*.on_soft_execute */ sofia_on_soft_execute,
/*.on_consume_media */ NULL,
/*.on_hibernate */ sofia_on_hibernate,
/*.on_reset */ sofia_on_reset,
/*.on_park */ NULL,
/*.on_reporting */ NULL,
/*.on_destroy */ sofia_on_destroy
};可见,sofia为各种channel状态定义了不同的处理函数。因此,此时会调用sofia_on_routing(session)函数。但是这个函数没有作本质性的工作。而switch_state_handler_table_t则是核心为各个endpoint设定的一个标准状态处理函数表结构。
2. 如果driver_state_handler没有定义状态处理函数,或者driver_state_handler中得处理函数被调用成功(例如sofia_on_routing),还需要根据do_extra_handler的值依次获取channel中得application_state_handler,调用该handler中得状态处理函数。即:
while(application_state_handler= switch_channel_get_state_handle (index++) )
application_state_handler->on_routing(session)//伪代码
在channel中有一个switch_state_handler_t 的指针数组state_handler,每一个元素指向一个switch_state_handler_t的实例,因此,application_state_hander会依次从该数组中获取指针,执行函数表中的相应的状态处理函数。
3. 若其中某个函数调用失败,说明有错,会设置proceed =0并跳出循环,这意味着,全局的gloabal_proceed也会被设置为0.
4. 在do_extra_handler的情况下,还需要依次取出runtime中得状态处理函数表指针数组runtime.state_handler[index]对各个处理函数表里相应的状态处理函数(如runtime.state_handler[index]->on_routing(session))作调用处理,若其中有调用失败,则设置proceed=0.并跳出去指针循环,去指针操作为:
while(application_state_handler= switch_core_get_state_handler(index++) )
application_state_handler->on_routing(session)//伪代码
同时还要设置global_proceed=0
5. 若存在调用失败或channel状态在处理过程中改变,需要设置global_proceed = 0,意味着不需要再往下处理了。
6. 若此时global_proceed==1 意味着需要往下处理,这调用标准核心函数
switch_core_standard_on_routing(session)
进行处理。
综上,当1002(即为in_bound )对于channel为on_routing状态时,实际的处理流程为:
① 调用driver_state_handler->on_routing(session),即sofia_on_routing函数,此函数不做实质性工作,只是走一个过场。
② 查询channle->state_handler[index],
调用(channle->state_handler[index])->on_routing(session)
此时channle中没有相应的状态处理函数表。
③ 查询runtime.state_handler[index],
调用(runtime.state_handler[index])->on_routing(session)
此时runtime中有一个相应的状态处理函数表,但函数表中没有定义相应的on_routing处理函数。
④ 此时没有调用失败的发生,并且sofia_on_routing是调用成功的,因此gloabal_proceed==1,会调用switch_core_standard_on_routing(session)函数作状态处理,而实际上,真正对in_bound session 的on_routing状态的处理正是在该函数中完成的。
In_bound channel的状态处理
三个代表性的转换状态如下:CS_INIT状态
当1002 进入channel的状态循环后,在sofia_handle_sip_i_state(…)处理 该session的“100 Trying”状态时,会根据sip消息与FS配置的媒体信息进行媒体协商,若协商成功,将设置channel状态为CS_INIT。
对于CS_INIT状态,FS同样通过STATE_MACRO宏进行处理。
① 调用sofia_on_init(…)函数,由于此时是in_bound call ,该函数做了一番检测后,只是调用switch_channel_set_state(…)函数将channel的状态设置为CS_ROUTING.
② 查询channle->state_handler[index],为空。
③ 查询runtime.state_handler[index],有一个处理函数表,但没有on_init(…)处理函数。
④ 调用switch_core_standard_on_init(session),该函数在此状态下为空函数。
CS_ROUTING状态
此时,实际有处理操作的是switch_core_standard_on_routing(session)函数:
① caller_profile =switch_channel_get_caller_profile(session->channel)
从channel中获取在sofia_handle_sip_i_invite中创建的caller_profile
② 获取caller_profile->dialplan,如前所述,此时的该变量为”XML”。
③ 根据caller_profile->dialplan在全局的dialplan_interface哈希表中查询对应的拨号计划interface。该interface是在mod_dialplan_xml模块加载时插入全局哈希表的,具体操作为:
dialplan_interface =switch_loadable_module_get_dialplan_interface(dpname)
④ 调用dialplan_interface的hunt_function从配置文件中获取extension。具体调用的是mod_dialplan_xml中得dialplan_funt函数,该函数会读取xml配置文件(dialplan/default.xml),获取匹配正则表达式的extension,extension在FS中用结构switch_caller_extension_t表述,获得extension后链入caller_profile中的switch_caller_extension_t*caller_extension链表。
⑤ 在成功的获取extension后,该函数将设置channel的状态为CS_EXCUTE。
CS_EXCUTE状态
对于in_bound call而言,channel->state_handler全为空,runtime.state_handler有一个函数处理表,但是没有对于CS_EXCUTE状态的处理函数。Sofia_on_excute(…)也没有做实质性工作,因此,最终还是switch_core_standard_on_excute(session)对该状态做了处理。
① 获取channel的extension,实质是通过channel->caller_profile->caller_extesion链表获取的。依次,此时获取的是一个链表,在B2B中,读取配置文件得到的extension链表中只有一个元素。具体的代码为:
extension =switch_channel_get_caller_extension(session->channel)
② 进入caller_extension链表的循环,依次读取extension,目前只有一个extension,另外,在循环中,还需要判断channel的状态是否保持在CS_EXCUTE,若在处理过程中改变了channel的状态,则会跳出循环,进行到channel的下一个状态处理中。
③ 在循环中,获取extension的application链表,具体代码为:
current_application= extension->current_application
在处理application时,也是通过查询哈希表的方式进行的,全局有一个app的hash,每个模块在加载时都会提供app函数并插入到该全局哈希表,而switch_caller_application_t
结构只负责记录从呼叫计划中获取的需要执行的app函数名和相关参数。
执行app的函数是switch_core_session_excute_application(…),该函数有三个形参,session指针,app名称,和app参数。在该函数中,会查询app hash表,获取application_interface,进而调用switch_core_session_exec(…),然后后用传入的app参数调用相应的app函数。
在进入switch_core_session_exec(…)函数后,有一个app函数为“bridge”,这里是in_boundcall 与 out_bound call 连接的桥梁。
在xml文件中,B2B的extension里定义了一个元素为:
<action application="bridge"data="user/${dialed_extension}@${domain_name}"/>
因此,在switch_core_session_exec(…)实参为对应”bridge”的application_interafce以及字符串”user/${dialed_extension}@${domain_name}”,在该函数内利用:
Switch_channel_expand_variables(…)函数对该字符串进行扩展,扩展成:
user/1003@192.168.1.***这里后面的是目的地ip。${dialed_extension},与${domain_name}体现在FS中是两个channel变量,在扩展时会对channel用switch_channel_get_variable函数进行获取,而dialed_extesion与domain_name都是在sofia_handle_sip_i_invite中设置到channel中的。获取以及扩展后,新的字符串为expanded=user/1003@192.168.1.***,这里将会利用该字符串作为参数作如下调用:
application_interface->application_function(session,expanded);
application_interface便是根据“bridge”从全局哈希表中获取的app接口,
application_function便是要调用的app函数。这里的实际函数为audio_bridge_function(…),
它是由mod_dptools模块提供的一个app函数,也是一个开始产生out_boundcall的起点。
对其需要做另外的分析。