freeswitch系列21模块sofia

  1. mod_sofia加载

在可加载模块那一章节说过,一个模块的加载,主要是调用load函数,也可以理解为模块初始化函数,下面分析下,mod_sofia加载做了哪些事。

 

    1. 全局结构体mod_sofia_globals

mod_sofia.c开头定义了两个全局结构体,其中一个是端点接口,前面也说过,sofia是一类最重要的端口。

 

  1. struct mod_sofia_globals mod_sofia_globals;  
  2. switch_endpoint_interface_t *sofia_endpoint_interface;  
  3.  
  1. struct mod_sofia_globals {  
  2.     switch_memory_pool_t *pool;  
  3.     switch_hash_t *profile_hash;  
  4.     switch_hash_t *gateway_hash;  
  5.     switch_mutex_t *hash_mutex;  
  6.     uint32_t callid;  
  7.     int32_t running;  
  8.     int32_t threads;  
  9.     int cpu_count;  
  10.     int max_msg_queues;  
  11.     switch_mutex_t *mutex;  
  12.     char guess_ip[80];  
  13.     char hostname[512];  
  14.     switch_queue_t *presence_queue;  
  15.     switch_queue_t *msg_queue;  
  16.     switch_queue_t *general_event_queue;  
  17.     switch_thread_t *msg_queue_thread[SOFIA_MAX_MSG_QUEUE];  
  18.     int msg_queue_len;  
  19.     struct sofia_private destroy_private;  
  20.     struct sofia_private keep_private;  
  21.     int guess_mask;  
  22.     char guess_mask_str[16];  
  23.     int debug_presence;  
  24.     int debug_sla;  
  25.     int auto_restart;  
  26.     int reg_deny_binding_fetch_and_no_lookup; /* backwards compatibility */  
  27.     int auto_nat;  
  28.     int tracelevel;  
  29.     char *capture_server;  
  30.     int rewrite_multicasted_fs_path;  
  31.     int presence_flush;  
  32.     switch_thread_t *presence_thread;  
  33.     uint32_t max_reg_threads;  
  34.     time_t presence_epoch;  
  35.     int presence_year;  
  36. };  
  37. extern struct mod_sofia_globals mod_sofia_globals; 

 

这里比较重要的几个成员说明一下,sip默认有4个profile,internal、internal-ipv6、external、external-ipv6。通过解析配置文件,可以把这4个profile添加到哈希表profile_hash,gateway_hash是网关列表,后面用到再补充说明。msg_queue消息是最重要的成员,所有从sip信令过来的消息,都会添加到消息队列。msg_queue_thread是个线程池,可以多个线程来处理消息。

    1. 初始化流程

1、首先是初始化一些变量。

  1. SWITCH_MODULE_LOAD_FUNCTION(mod_sofia_load)  
  2. {  
  3.     switch_chat_interface_t *chat_interface;  
  4.     switch_api_interface_t *api_interface;  
  5.     switch_management_interface_t *management_interface;  
  6.     switch_application_interface_t *app_interface;  
  7.     struct in_addr in;  
  8.     switch_status_t status;  
  9.   
  10.     memset(&mod_sofia_globals, 0, sizeof(mod_sofia_globals));  
  11.     mod_sofia_globals.destroy_private.destroy_nh = 1;  
  12.     mod_sofia_globals.destroy_private.is_static = 1;  
  13.     mod_sofia_globals.keep_private.is_static = 1;  
  14.     mod_sofia_globals.pool = pool;  
  15.     switch_mutex_init(&mod_sofia_globals.mutex, SWITCH_MUTEX_NESTED, mod_sofia_globals.pool);  
  16.     switch_core_hash_init(&mod_sofia_globals.profile_hash);  
  17.     switch_core_hash_init(&mod_sofia_globals.gateway_hash);  
  18.     switch_mutex_init(&mod_sofia_globals.hash_mutex, SWITCH_MUTEX_NESTED, mod_sofia_globals.pool);  

 

  1. 注册一些事件,关于事件后面再补充

 

  1. if (switch_event_reserve_subclass(MY_EVENT_NOTIFY_REFER) != SWITCH_STATUS_SUCCESS) {  
  2.     switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register subclass %s!\n", MY_EVENT_NOTIFY_REFER);  
  3.     switch_goto_status(SWITCH_STATUS_TERM, err);  
  4. }  
  5.   
  6. if (switch_event_reserve_subclass(MY_EVENT_NOTIFY_WATCHED_HEADER) != SWITCH_STATUS_SUCCESS) {  
  7.     switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register subclass %s!\n", MY_EVENT_NOTIFY_WATCHED_HEADER);  
  8.     switch_goto_status(SWITCH_STATUS_TERM, err);  
  9. }  
  10. ...

 

  1. 创建消息队列

switch_queue_create(&mod_sofia_globals.msg_queue, SOFIA_MSG_QUEUE_SIZE * mod_sofia_globals.max_msg_queues, mod_sofia_globals.pool); 

 

  1. sip初始化
  1. if (sofia_init() != SWITCH_STATUS_SUCCESS) {  
  2.     switch_goto_status(SWITCH_STATUS_GENERR, err);  
  3.     return SWITCH_STATUS_GENERR;  
  4. }  
  5.   
  6. switch_status_t sofia_init(void)  
  7. {  
  8.     su_init();  
  9.     if (sip_update_default_mclass(sip_extend_mclass(NULL)) < 0) {  
  10.         su_deinit();  
  11.         return SWITCH_STATUS_GENERR;  
  12.     }  
  13.   
  14. #ifdef SOFIA_TIME  
  15.     su_set_time_func(sofia_time);  
  16. #endif  
  17.   
  18.     /* Redirect loggers in sofia */  
  19.     su_log_redirect(su_log_default, logger, NULL);  
  20.     su_log_redirect(tport_log, logger, NULL);  
  21.     su_log_redirect(iptsec_log, logger, NULL);  
  22.     su_log_redirect(nea_log, logger, NULL);  
  23.     su_log_redirect(nta_log, logger, NULL);  
  24.     su_log_redirect(nth_client_log, logger, NULL);  
  25.     su_log_redirect(nth_server_log, logger, NULL);  
  26.     su_log_redirect(nua_log, logger, NULL);  
  27.     su_log_redirect(soa_log, logger, NULL);  
  28.     su_log_redirect(sresolv_log, logger, NULL);  
  29. #ifdef HAVE_SOFIA_STUN  
  30.     su_log_redirect(stun_log, logger, NULL);  
  31. }
  32. #endif  
  33.   
  34.     return SWITCH_STATUS_SUCCESS;  

 

sip初始化,主要是调用协议栈库的su_init()进行初始化。

 

  1. 解析sip profile配置
  1. if (config_sofia(SOFIA_CONFIG_LOAD, NULL) != SWITCH_STATUS_SUCCESS) {  
  2.     mod_sofia_globals.running = 0;  
  3.     switch_goto_status(SWITCH_STATUS_GENERR, err);  
  4.     return SWITCH_STATUS_GENERR;  
  5. }

 

这个函数很长,也比较重要,等下单独一个小节分析,这里继续主线初始化。

 

  1. 创建消息处理线程

sofia_msg_thread_start(0);

前面创建了消息队列,这样就有专门处理消息的线程,该线程单独一个大节来分析。

 

  1. 创建接口
  1. /* connect my internal structure to the blank pointer passed to me */  
  2. *module_interface = switch_loadable_module_create_module_interface(pool, modname);  
  3. sofia_endpoint_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_ENDPOINT_INTERFACE);  
  4. sofia_endpoint_interface->interface_name = "sofia";  
  5. sofia_endpoint_interface->io_routines = &sofia_io_routines;  
  6. sofia_endpoint_interface->state_handler = &sofia_event_handlers;  
  7. sofia_endpoint_interface->recover_callback = sofia_recover_callback;  
  8.   
  9. management_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_MANAGEMENT_INTERFACE);  
  10. management_interface->relative_oid = "1001";  
  11. management_interface->management_function = sofia_manage;  
  12.   
  13. SWITCH_ADD_APP(app_interface, "sofia_sla""private sofia sla function",  
  14.                "private sofia sla function", sofia_sla_function, "<uuid>", SAF_NONE);  
  15.   
  16.   
  17. SWITCH_ADD_API(api_interface, "sofia""Sofia Controls", sofia_function, "<cmd> <args>");  

 

这是最重要的一步了,这里会先创建一个模块接口,然后用模块接口创建端点接口,端点接口重要的两个成员是sofia_io_routines和sofia_event_handlers。后面又创建了管理接口,添加了若干个APP和API。

 

9、crtp初始化

crtp_init(*module_interface);

最后一步,调用crtp_init,这个的作用还没研究到,后面研究再补充文档。

    1. 解析sip_profile

上一节的mod_sofia初始化中,有个步骤调用config_sofia(SOFIA_CONFIG_LOAD, NULL)去解析sip_profile,现在来分析下这个函数,这个函数很长,有近2000行,需要分步分析。

 

  1. switch_status_t config_sofia(sofia_config_t reload, char *profile_name)  
  2. {  
  3.     char *cf = "sofia.conf";  
  4.   
  5.     if (!(xml = switch_xml_open_cfg(cf, &cfg, params))) {  
  6.         switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", cf);  
  7.         status = SWITCH_STATUS_FALSE;  
  8.         goto done;  
  9.     }  
  10. ...  
  11. }

 

从上面可以看出,首先解析sofia.conf这个配置文件。这个配置只解析两个字段,global_settings和profiles,其中profiles很长,基本上都是在解析这个字段。在看代码的时候,可以对比autoload_configs/sofia.conf这个文件。

 

  1.     if ((settings = switch_xml_child(cfg, "global_settings"))) {  
  2.         for (param = switch_xml_child(settings, "param"); param; param = param->next) {  
  3.             char *var = (char *) switch_xml_attr_soft(param, "name");  
  4.             char *val = (char *) switch_xml_attr_soft(param, "value");  
  5.             if (!strcasecmp(var, "log-level")) {  
  6.                 su_log_set_level(NULL, atoi(val));  
  7.             } else if ...  
  8.     }  
  9.   
  10.     if ((profiles = switch_xml_child(cfg, "profiles"))) {  
  11. ...  
  12. }  

 

解析xml字段的原理比较简单,这里以解析出gobal_settings中的log-level字段为例,解析出来后调用su_log_set_level,设置协议栈的日志等级。

接下来解析很长的profile字段,这个字段在sip_profiles文件夹中的各个文件,这里以internal.xml为例。

 

  1. if ((profiles = switch_xml_child(cfg, "profiles"))) {  
  2.     for (xprofile = switch_xml_child(profiles, "profile"); xprofile; xprofile = xprofile->next) {  
  3.         char *xprofilename = (char *) switch_xml_attr_soft(xprofile, "name");  
  4.         char *xprofiledomain = (char *) switch_xml_attr(xprofile, "domain");  
  5.         if (!(settings = switch_xml_child(xprofile, "settings"))) {  
  6.          }  
  7.    }  

 

这里基本上和配置文件对应,其中settings的解析很长,一般的配置这里就不一一分析了。

 

  1. if (!profile_already_started) {  
  2.   
  3.     /* Setup the pool */  
  4.     if ((status = switch_core_new_memory_pool(&pool)) != SWITCH_STATUS_SUCCESS) {  
  5.         switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Memory Error!\n");  
  6.         sofia_profile_start_failure(NULL, xprofilename);  
  7.         goto done;  
  8.     }  
  9.   
  10.     if (!(profile = (sofia_profile_t *) switch_core_alloc(pool, sizeof(*profile)))) {  
  11.         switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Memory Error!\n");  
  12.         sofia_profile_start_failure(NULL, xprofilename);  
  13.         goto done;  
  14.     }  
  15.   
  16.     profile->tls_verify_policy = TPTLS_VERIFY_NONE;  
  17.     /* lib default */  
  18.     profile->tls_verify_depth = 2;  
  19.     switch_mutex_init(&profile->gw_mutex, SWITCH_MUTEX_NESTED, pool);  
  20.     profile->trans_timeout = 100;  
  21.     profile->auto_rtp_bugs = RTP_BUG_CISCO_SKIP_MARK_BIT_2833;// | RTP_BUG_SONUS_SEND_INVALID_TIMESTAMP_2833;  
  22.     profile->pool = pool;  
  23. ...

 

如果没有解析过这个profile,则申请sofia_profile_t结构体内存,并初始化一些参数。

 

  1. for (param = switch_xml_child(settings, "param"); param; param = param->next) {  
  2.     char *var = (char *) switch_xml_attr_soft(param, "name");  
  3.     char *val = (char *) switch_xml_attr_soft(param, "value");  
  4.     int found = 1; // Used to break up long if/elseif chain (MSVC2015 fails (parser stack overflow) otherwise)  
  5.   
  6.     switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s [%s]\n", var, val);  
  7.       
  8.     if (!strcasecmp(var, "debug") && val) {  
  9.         profile->debug = atoi(val);  
  10.  

 

解析settings字段中的各个param。这里以debug为例,存到profile->debug变量。比较重要的变量有odbc_dsn,这里可以配置sip相关的数据库存到哪里。所以修改sip配置文件的odbc-dsn字段,指向mysql数据库,就会在这里赋值。

 

  1. else if (!strcasecmp(var, "odbc-dsn") && !zstr(val)) {  
  2.         profile->odbc_dsn = switch_core_strdup(profile->pool, val);  

 

解析完所有settings参数后,会进行最后的处理。

 

  1. if (profile) {  
  2.     if (profile_already_started) {  
  3.     }  
  4.     else {  
  5.       launch_sofia_profile_thread(profile);  
  6.   }  
  7. }  

 

第一次启动新的线程来处理该profile的逻辑。到这里就解析完config_sofia,这里需要注意一点,sip默认有4个profile,internal、internal-ipv6、external、external-ipv6,所以会启动4条sofia_profile_thread_run线程,4个UA互不干扰。

  1. profile线程sofia_profile_thread_run
  1. void *SWITCH_THREAD_FUNC sofia_profile_thread_run(switch_thread_t *threadvoid *obj)  
  2. {  
  3.     sofia_profile_t *profile = (sofia_profile_t *) obj;  
  4. ...  
  5.   
  6.     //线程数加1  
  7.     switch_mutex_lock(mod_sofia_globals.mutex);  
  8.     mod_sofia_globals.threads++;  
  9.     switch_mutex_unlock(mod_sofia_globals.mutex);  
  10.   
  11.     //跟sip协议栈相关的,  
  12.     profile->s_root = su_root_create(NULL);  
  13.   
  14.     //初始化sip的数据库,创建那些表  
  15.     sofia_glue_init_sql(profile);  
  16.   
  17.     //重要,这里就创建了一个UA代理,其中sofia_event_callback是很重要的回调,当sip收到协议包或状态改变,通过此回调通知应用  
  18.     do {  
  19.         profile->nua = nua_create(profile->s_root,    /* Event loop */  
  20.                                   sofia_event_callback, /* Callback for processing events */  
  21.                                   profile,  /* Additional data to pass to callback */  
  22.                                   ...  
  23.     }  
  24.   
  25.     //设置一些参数  
  26.     nua_set_params(profile->nua,...);  
  27.   
  28.     //创建数据库的执行队列,数据库的操作是在单独的一条线程  
  29.     switch_sql_queue_manager_init_name(qname,  
  30.                                        &profile->qm,  
  31.                                        2,  
  32.                                        profile->odbc_dsn ? profile->odbc_dsn : profile->dbname,  
  33.                                        SWITCH_MAX_TRANS,  
  34.                                        profile->pre_trans_execute,  
  35.                                        profile->post_trans_execute,  
  36.                                        profile->inner_pre_trans_execute,  
  37.                                        profile->inner_post_trans_execute);  
  38.     switch_sql_queue_manager_start(profile->qm);  
  39.   
  40.     //添加profile  
  41.     sofia_glue_add_profile(profile->name, profile);  
  42.   
  43.     //创建工作线程  
  44.     worker_thread = launch_sofia_worker_thread(profile);  
  45.   
  46.     //此线程只产生定时脉冲,真实工作转由工作线程  
  47.     while (mod_sofia_globals.running == 1 && sofia_test_pflag(profile, PFLAG_RUNNING) && sofia_test_pflag(profile, PFLAG_WORKER_RUNNING)) {  
  48.         su_root_step(profile->s_root, 1000);  
  49.         profile->last_root_step = switch_time_now();  
  50.     }  
  51.   
  52.     //线程退出后的一些清理工作  
  53.     sofia_reg_unregister(profile);  
  54.     nua_shutdown(profile->nua);  
  55.     ...  

 

sip_profile线程干了几件重要的事:

  1. 调用协议栈API创建UA代理,并设置了回调sofia_event_callback,这个回调很重要,是sip通知应用的入口。
  2. 创建了sip数据库相关的资源、创建表、SQL队列等,数据库的操作是在单独的线程里执行的。
  3. 创建一条工作线程,把处理的任务转移到这条线程。
  4. 最后sip_profile只干定时产生时间脉冲的事。
  1. 工作线程sofia_profile_worker_thread_run
  1. void *SWITCH_THREAD_FUNC sofia_profile_worker_thread_run(switch_thread_t *threadvoid *obj)  
  2. {  

  

  1.         if (switch_queue_pop_timeout(mod_sofia_globals.general_event_queue, &pop, 100000) == SWITCH_STATUS_SUCCESS) {  
  2.               
  3.             do {  
  4.                 switch_event_t *event = (switch_event_t *) pop;  
  5.                 general_event_handler(event);  
  6.                 switch_event_destroy(&event);  
  7.   
  8.                 pop = NULL;  
  9.                 switch_queue_trypop(mod_sofia_globals.general_event_queue, &pop);  
  10.             } while (pop);  
  11.   
  12.         }  
  13. ...  
  14.     }  
  15.   
  16.     return NULL;  

工作线程剔除掉一些非重要代码,剩下最重要的就是从实际队列mod_sofia_globals.general_event_queue弹出一个事件对象,然后调用general_event_handler(event)处理该对象。

到这里就总结到两个重要点,sofia_event_callback处理sip消息,general_event_handler用来处理事件消息。另外还有一条线程还没讲,在加载模块时,sofia_msg_thread_start(0)会创建消息处理线程sofia_msg_thread_run,我们单独来分析下这条线程。

  1. 消息处理线程sofia_msg_thread_run
  1. void *SWITCH_THREAD_FUNC sofia_msg_thread_run(switch_thread_t *threadvoid *obj)  
  2. {  
  3.     void *pop;  
  4.     switch_queue_t *q = (switch_queue_t *) obj;  
  5.  

...  

  1.     for(;;) {  
  2.   
  3.         if (switch_queue_pop(q, &pop) != SWITCH_STATUS_SUCCESS) {  
  4.             switch_cond_next();  
  5.             continue;  
  6.         }  
  7.   
  8.         if (pop) {  
  9.             sofia_dispatch_event_t *de = (sofia_dispatch_event_t *) pop;  
  10.             sofia_process_dispatch_event(&de);  
  11.         } else {  
  12.             break;  
  13.         }  
  14.     }  
  15. ... 
  16.     return NULL;  
  17. }  

 

消息处理线程也很简单,也是从队列取出一个消息,然后调用sofia_process_dispatch_event去处理。这个队列是哪里来的,从创建线程的地方可以看到,是mod_sofia_globals.msg_queue。

 

这里总结下sip相关的三类线程。

sofia_msg_thread_run:sip消息处理线程,消息队列是mod_sofia_globals.msg_queue,它是全局的,但是不止一条,二是会根据cpu核数创建若干条,是线程池。

sofia_profile_thread_run:每个profile都会创建该线程,除了创建sip UA,只产生时钟脉冲,不做什么实事。

sofia_profile_worker_thread_run:也是每个profile创建一条线程,主要处理事件,事件队列是mod_sofia_globals.general_event_queue。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值