sofia模块在freeswitch中的位置非常重要, 所有的sip通话都和它有关, 那么我们就看一下该模块的执行流程。

一、 实现的功能:

1. sip注册;

2. 呼叫;

3. Presence;

4. SLA, 等。

 

二、 主要的方法, 有三个, 分别为:

 




 

1. #define SWITCH_MODULE_LOAD_FUNCTION(name) switch_status_t name SWITCH_MODULE_LOAD_ARGS  
2. #define SWITCH_MODULE_RUNTIME_FUNCTION(name) switch_status_t name SWITCH_MODULE_RUNTIME_ARGS  
3. #define SWITCH_MODULE_SHUTDOWN_FUNCTION(name) switch_status_t name SWITCH_MODULE_SHUTDOWN_ARGS


 

1. </pre><pre name="code" class="cpp">SWITCH_MODULE_LOAD_FUNCTION(mod_sofia_load);  
2. SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_sofia_shutdown);  
3. SWITCH_MODULE_DEFINITION(mod_sofia, mod_sofia_load, mod_sofia_shutdown, NULL);


 

这三个方法依次分别为:模块加载, 卸载 ,运行 。

 

三、 分析 mod_sofia_load方法:

1. sofia模块分别包含 api命令行可执行命令, application应用app, 即供拨号计划等其它模块调用的,提供完整功能的应用, 即时聊天的应用, management管理应用等实例, 并对其进行了定义, 并定义了mod_sofia_globals sofia全局数据中心实例, 并一一对依变量进行初始化, 结构如下:

 


 


    1. struct mod_sofia_globals {  
    2. switch_memory_pool_t *pool;  //内存池管理器  
    3. switch_hash_t *profile_hash;   //sip_profile的hash表  
    4. switch_hash_t *gateway_hash;  //internal或external的网关列表  
    5. switch_mutex_t *hash_mutex;   //本结构hash表互斥信号量  
    6. uint32_t callid;  //呼叫ID  
    7. int32_t running;  //是否已运行的标志  
    8. int32_t threads;   //线程号  
    9. int cpu_count;   //本机CPU个数  
    10. int max_msg_queues;  //最大的消息队列数  
    11. switch_mutex_t *mutex;  本结构互斥信号量  
    12. char guess_ip[80];  // nat环境下, 智能分析出的可访问地址  
    13. char hostname[512];  //分析出的本机名称  
    14. switch_queue_t *presence_queue; //状态队列  
    15. switch_queue_t *msg_queue; //sip消息队列  
    16. switch_thread_t *msg_queue_thread[SOFIA_MAX_MSG_QUEUE];  //不同队列所对应的分析线程句柄数组  
    17. int msg_queue_len;   //消息队列的长度  
    18. struct sofia_private destroy_private;     
    19. struct sofia_private keep_private;  
    20. int guess_mask;  
    21. char guess_mask_str[16];   
    22. int debug_presence; //调试的状态标志, 从sofia.conf.xml文件中读取  
    23. int debug_sla;  //调试的路由域标志  
    24. int auto_restart;  //是否自动重启的标志,  从sofia.conf.xml文件中读取  
    25. int reg_deny_binding_fetch_and_no_lookup; /* backwards compatibility */  
    26. int auto_nat; //是否自动nat环境分析, 从sofia.conf.xml文件中读取  
    27. int tracelevel; //日志等级, 从sofia.conf.xml文件中读取  
    28. char *capture_server;   
    29. int rewrite_multicasted_fs_path;    
    30. int presence_flush;  
    31. switch_thread_t *presence_thread;  //状态处理线程句柄  
    32. uint32_t max_reg_threads;  //最大注册线程数  
    33. time_t presence_epoch;   
    34. };


     

    mod_sofia_globals这个全局数据集使用了系统中的内存池, 分别创建了状态队列,  消息队列.

     

    2.  开启对消息队列的监听及处理, 事件的分发:

    先进入sofia_msg_thread_start(int idx)方法, 循环创建 msg_queue_len 大小个 任务处理实体, 并分别分配了线程上下文数据, 交由 任务方法 sofia_msg_thread_run 去一直死循环POP队列数据, , 取出 sofia_dispatch_event_t 事件并交由sofia_process_dispatch_event方法进行事件分发处理。

     

    3. 回调注册的事件处理句柄,针对具体事件进行处理。

    事件接收线程将事件的必要参数取出来后, 直接调用

     


       
    1. static void our_sofia_event_callback(nua_event_t event,  
    2. int status,  
    3. char const *phrase,  
    4. const *sip,  
    5. sofia_dispatch_event_t *de, tagi_t tags[])


    方法进行具体的事件处理。


     

     

    4. 这个回调方法主要处理了:

      通过事件消息获取 session等相关数据, 并设置到channel中去;

      判断该命令发起的用户是否已通过了鉴权认证, 如果认证未通过, 则直接返回401或407要求重新鉴权,  如果认证通过了, 则直接修改本channel的 sip_authorized标志, 设置为true, 表示通过认证, 下次事件消息过来后, 就直接放行。 如果事件状态码为401或407, 表明本服务需要向对方进行认证, 便通过sofia_reg_handle_sip_r_challenge方法进行验证。

      接下来就是switch各种sip信令进行不同的处理分支, 实际上有很多种,其中以nua_r打头的消息都是收到了一条响应消息, 以nua_i打头的消息是收到了一条请求消息, 我们只关心nua_i_invite(呼叫相关), nua_i_option(心跳保持), nua_i_register(注册)。

     

     5.  其中sofia_handle_sip_i_invite方法中进行了一次呼叫发起的执行, 先进行call数据是否达上限的一个保护逻辑, 如果超过则直接返回503, 再判断是不是无效的sip包是则返回400,  如果是SDP消息, 则调用core中的 switch_core_media_set_sdp_codec_string 方法进行处理其结果存于session中, 再判断nat类型是"via received" or “via host" or "via port" 分别记录到is_nat中去, 再判断是否有权限, 如果无, 则直接返回403, 否则继续, 再断续填充channel相关参数, 再设置IVR用户相关数据,  再校验被叫号码是否合法,前面一堆都是针对channel的各种状态设置及各种状态交换的保存, 以前主叫方的数据获取, 并设置桥接profile, 通过switch_channel_set_originatee_caller_profile方法进行。

       其中有针对几张数据表的操作:

     


     

    1. </pre><pre name="code" class="cpp">switch_mprintf("select 'appearance-index=1' from sip_subscriptions where expires > -1 and hostname='%q' and event='call-info' and "  
    2. "sub_to_user='%q' and sub_to_host='%q'", mod_sofia_globals.hostname, sip->sip_to->a_url->url_user,  
    3.  sip->sip_from->a_url->url_host);


     


      1. </pre>对sip_subscriptions的操作, 查询用户是否在线。</p><p>  </p><p>如果在线, 则直接更新sip_dialogs的呼叫信息及吃叫信息状态字段, 通过session的uuid.</p><p><pre name="code" class="cpp">sql = switch_mprintf("update sip_dialogs set call_info='%q',call_info_state='%q' "  
      2. "where uuid='%q'", buf, state, switch_core_session_get_uuid(session));



      从sip_dialogs表中查询callid信息


       

      1. sql =  
      2. switch_mprintf  
      3. ("select call_id from sip_dialogs where call_info='%q' and ((sip_from_user='%q' and sip_from_host='%q') or presence_id='%q@%q') "  
      4. "and call_id is not null",  
      5.  switch_str_nil(p), user, host, user, host);


      通过call_id更新表sip_dialogs的call_info_state字段为idle状态


       

       

      1. char *sql = switch_mprintf("update sip_dialogs set call_info_state='idle' where call_id='%q'", b_call_id);


      向sip_dialogs表中插入呼叫相关信息



       

      1. sql = switch_mprintf("insert into sip_dialogs "  
      2. "(call_id,uuid,sip_to_user,sip_to_host,sip_to_tag,sip_from_user,sip_from_host,sip_from_tag,contact_user,"  
      3. "contact_host,state,direction,user_agent,profile_name,hostname,contact,presence_id,presence_data,"  
      4. "call_info,rcd,call_info_state) "  
      5. "values('%q','%q','%q','%q','%q','%q','%q','%q','%q','%q','%q','%q','%q','%q','%q','%q','%q','%q','%q',%ld,'')",  
      6.  call_id,  
      7. tech_pvt->sofia_private->uuid,  
      8. to_user, to_host, to_tag, dialog_from_user, dialog_from_host, from_tag,  
      9. contact_user, contact_host, "confirmed", "inbound", user_agent,  
      10. profile->name, mod_sofia_globals.hostname, switch_str_nil(full_contact),  
      11. switch_str_nil(presence_id), switch_str_nil(presence_data), switch_str_nil(p), now);


      6.  SIP状态机的分析, nua_i_state, 状态机也是和invate等其它事件一样的通过our_sofia_event_callback方法进入到事件选择处理方法中。


       

       

       

        1. case nua_i_state:  
        2. sofia_handle_sip_i_state(session, status, phrase, nua, profile, nh, sofia_private, sip, de, tags);


         

        1. if (r_sdp && !sofia_test_flag(tech_pvt, TFLAG_SDP)) {  
        2. if (switch_channel_test_flag(channel, CF_PROXY_MODE)) {  
        3. "RECEIVED_NOMEDIA");  
        4.                     sofia_set_flag_locked(tech_pvt, TFLAG_READY);  
        5. if (switch_channel_get_state(channel) == CS_NEW) {  
        6.                         switch_channel_set_state(channel, CS_INIT);  
        7.                     }  
        8.                     sofia_set_flag(tech_pvt, TFLAG_SDP);  
        9. goto done;  
        10. else if (switch_channel_test_flag(tech_pvt->channel, CF_PROXY_MEDIA)) {  
        11. "PROXY MEDIA");  
        12.                     sofia_set_flag_locked(tech_pvt, TFLAG_READY);  
        13. if (switch_channel_get_state(channel) == CS_NEW) {  
        14.                         switch_channel_set_state(channel, CS_INIT);  
        15.                     }  
        16. else if (sofia_test_flag(tech_pvt, TFLAG_LATE_NEGOTIATION)) {  
        17. "DELAYED NEGOTIATION");  
        18.                     sofia_set_flag_locked(tech_pvt, TFLAG_READY);  
        19. if (switch_channel_get_state(channel) == CS_NEW) {  
        20.                         switch_channel_set_state(channel, CS_INIT);  
        21.                     }  
        22.                 }


        类似这样, 有相应的状态事件过来后, 找到对应的channel, 跟据当前的状态值, 直接推断后续的状态, 并更新状态机。


         

         

        7. CHANNEL状态机分析

          只要channel的状态一变成CS_INIT, freeswitch核心的状态机就会负责处理各种状态变化了, 因而各Endpoint模块就不需要再自已维护状态机了。也就是说在Endpoint模块中跟踪channel状态机的变化, 这就需要靠在核心状态机上注册相应的回调函数实现。 回调方法如下:

         


         

        1. switch_state_handler_table_t sofia_event_handlers = {  
        2. /*.on_init */ sofia_on_init,  
        3. /*.on_routing */ sofia_on_routing,  
        4. /*.on_execute */ sofia_on_execute,  
        5. /*.on_hangup */ sofia_on_hangup,  
        6. /*.on_exchange_media */ sofia_on_exchange_media,  
        7. /*.on_soft_execute */ sofia_on_soft_execute,  
        8. /*.on_consume_media */ NULL,  
        9. /*.on_hibernate */ sofia_on_hibernate,  
        10. /*.on_reset */ sofia_on_reset,  
        11. /*.on_park */ NULL,  
        12. /*.on_reporting */ NULL,  
        13. /*.on_destroy */ sofia_on_destroy  
        14. };

        四、 分析 mod_sofia_shutdown方法:


          这里是针对sofia模块申请资源的一种释放, 主要包括线程, 内存, 及状态值, 方法如下:

         

         

        1. SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_sofia_shutdown)  
        2. {  
        3. int sanity = 0;  
        4. int i;  
        5.     switch_status_t st;  
        6.   
        7. "::sofia::list_profiles");  
        8. "del sofia");  
        9.   
        10.     switch_mutex_lock(mod_sofia_globals.mutex);  
        11. if (mod_sofia_globals.running == 1) {  
        12. //改成非运行状态  
        13.     }  
        14.     switch_mutex_unlock(mod_sofia_globals.mutex);  
        15.           
        16. //卸载状态事件回调方法  
        17.   
        18. //<span style="font-family: Arial, Helvetica, sans-serif;">卸载全局事件回调方法</span>  
        19.   
        20.     switch_event_unbind_callback(event_handler);   
        21.   
        22.     switch_queue_push(mod_sofia_globals.presence_queue, NULL);  
        23. //终断所有执行POPO列队的任务线程   
        24.   
        25. while (mod_sofia_globals.threads) {     
        26.         switch_cond_next();  
        27. if (++sanity >= 60000) {  
        28. break;  
        29.         }  
        30.     }  
        31.   
        32.   
        33. for (i = 0; mod_sofia_globals.msg_queue_thread[i]; i++) {  
        34.         switch_queue_push(mod_sofia_globals.msg_queue, NULL);  
        35.         switch_queue_interrupt_all(mod_sofia_globals.msg_queue);  
        36.     }  
        37.   
        38.   
        39. for (i = 0; mod_sofia_globals.msg_queue_thread[i]; i++) {  
        40.         switch_thread_join(&st, mod_sofia_globals.msg_queue_thread[i]);  
        41.     }  
        42.   
        43. if (mod_sofia_globals.presence_thread) {  
        44.         switch_thread_join(&st, mod_sofia_globals.presence_thread);  
        45.     }  
        46.   
        47. //switch_yield(1000000);  
        48.     su_deinit();  
        49.   
        50.     switch_mutex_lock(mod_sofia_globals.hash_mutex);  
        51.     switch_core_hash_destroy(&mod_sofia_globals.profile_hash);  
        52.     switch_core_hash_destroy(&mod_sofia_globals.gateway_hash);  
        53.     switch_mutex_unlock(mod_sofia_globals.hash_mutex);  
        54.   
        55. return SWITCH_STATUS_SUCCESS;  
        56. }



         

        五、 分析 模块方法的定义声明, SWITCH_MODULE_DEFINITION, :

         



          
        1. #define SWITCH_MODULE_DEFINITION_EX(name, load, shutdown, runtime, flags)                   \  
        2. static const char modname[] =  #name ;                                                      \  
        3. SWITCH_MOD_DECLARE_DATA switch_loadable_module_function_table_t name##_module_interface = { \  
        4.     SWITCH_API_VERSION,                                                                     \  
        5.     load,                                                                                   \  
        6.     shutdown,                                                                               \  
        7.     runtime,                                                                                \  
        8.     flags                                                                                   \  
        9. }

        将三个方法被始化到 switch_loadable_module_function_table_t  mod_sofia_module_interface 变量中去。