- mod_sofia加载
在可加载模块那一章节说过,一个模块的加载,主要是调用load函数,也可以理解为模块初始化函数,下面分析下,mod_sofia加载做了哪些事。
-
- 全局结构体mod_sofia_globals
mod_sofia.c开头定义了两个全局结构体,其中一个是端点接口,前面也说过,sofia是一类最重要的端口。
- struct mod_sofia_globals mod_sofia_globals;
- switch_endpoint_interface_t *sofia_endpoint_interface;
- struct mod_sofia_globals {
- switch_memory_pool_t *pool;
- switch_hash_t *profile_hash;
- switch_hash_t *gateway_hash;
- switch_mutex_t *hash_mutex;
- uint32_t callid;
- int32_t running;
- int32_t threads;
- int cpu_count;
- int max_msg_queues;
- switch_mutex_t *mutex;
- char guess_ip[80];
- char hostname[512];
- switch_queue_t *presence_queue;
- switch_queue_t *msg_queue;
- switch_queue_t *general_event_queue;
- switch_thread_t *msg_queue_thread[SOFIA_MAX_MSG_QUEUE];
- int msg_queue_len;
- struct sofia_private destroy_private;
- struct sofia_private keep_private;
- int guess_mask;
- char guess_mask_str[16];
- int debug_presence;
- int debug_sla;
- int auto_restart;
- int reg_deny_binding_fetch_and_no_lookup; /* backwards compatibility */
- int auto_nat;
- int tracelevel;
- char *capture_server;
- int rewrite_multicasted_fs_path;
- int presence_flush;
- switch_thread_t *presence_thread;
- uint32_t max_reg_threads;
- time_t presence_epoch;
- int presence_year;
- };
- 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、首先是初始化一些变量。
- SWITCH_MODULE_LOAD_FUNCTION(mod_sofia_load)
- {
- switch_chat_interface_t *chat_interface;
- switch_api_interface_t *api_interface;
- switch_management_interface_t *management_interface;
- switch_application_interface_t *app_interface;
- struct in_addr in;
- switch_status_t status;
- memset(&mod_sofia_globals, 0, sizeof(mod_sofia_globals));
- mod_sofia_globals.destroy_private.destroy_nh = 1;
- mod_sofia_globals.destroy_private.is_static = 1;
- mod_sofia_globals.keep_private.is_static = 1;
- mod_sofia_globals.pool = pool;
- switch_mutex_init(&mod_sofia_globals.mutex, SWITCH_MUTEX_NESTED, mod_sofia_globals.pool);
- switch_core_hash_init(&mod_sofia_globals.profile_hash);
- switch_core_hash_init(&mod_sofia_globals.gateway_hash);
- switch_mutex_init(&mod_sofia_globals.hash_mutex, SWITCH_MUTEX_NESTED, mod_sofia_globals.pool);
- 注册一些事件,关于事件后面再补充
- if (switch_event_reserve_subclass(MY_EVENT_NOTIFY_REFER) != SWITCH_STATUS_SUCCESS) {
- switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register subclass %s!\n", MY_EVENT_NOTIFY_REFER);
- switch_goto_status(SWITCH_STATUS_TERM, err);
- }
- if (switch_event_reserve_subclass(MY_EVENT_NOTIFY_WATCHED_HEADER) != SWITCH_STATUS_SUCCESS) {
- switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register subclass %s!\n", MY_EVENT_NOTIFY_WATCHED_HEADER);
- switch_goto_status(SWITCH_STATUS_TERM, err);
- }
- ...
- 创建消息队列
switch_queue_create(&mod_sofia_globals.msg_queue, SOFIA_MSG_QUEUE_SIZE * mod_sofia_globals.max_msg_queues, mod_sofia_globals.pool);
- sip初始化
- if (sofia_init() != SWITCH_STATUS_SUCCESS) {
- switch_goto_status(SWITCH_STATUS_GENERR, err);
- return SWITCH_STATUS_GENERR;
- }
- switch_status_t sofia_init(void)
- {
- su_init();
- if (sip_update_default_mclass(sip_extend_mclass(NULL)) < 0) {
- su_deinit();
- return SWITCH_STATUS_GENERR;
- }
- #ifdef SOFIA_TIME
- su_set_time_func(sofia_time);
- #endif
- /* Redirect loggers in sofia */
- su_log_redirect(su_log_default, logger, NULL);
- su_log_redirect(tport_log, logger, NULL);
- su_log_redirect(iptsec_log, logger, NULL);
- su_log_redirect(nea_log, logger, NULL);
- su_log_redirect(nta_log, logger, NULL);
- su_log_redirect(nth_client_log, logger, NULL);
- su_log_redirect(nth_server_log, logger, NULL);
- su_log_redirect(nua_log, logger, NULL);
- su_log_redirect(soa_log, logger, NULL);
- su_log_redirect(sresolv_log, logger, NULL);
- #ifdef HAVE_SOFIA_STUN
- su_log_redirect(stun_log, logger, NULL);
- }
- #endif
- return SWITCH_STATUS_SUCCESS;
- }
sip初始化,主要是调用协议栈库的su_init()进行初始化。
- 解析sip profile配置
- if (config_sofia(SOFIA_CONFIG_LOAD, NULL) != SWITCH_STATUS_SUCCESS) {
- mod_sofia_globals.running = 0;
- switch_goto_status(SWITCH_STATUS_GENERR, err);
- return SWITCH_STATUS_GENERR;
- }
这个函数很长,也比较重要,等下单独一个小节分析,这里继续主线初始化。
- 创建消息处理线程
sofia_msg_thread_start(0);
前面创建了消息队列,这样就有专门处理消息的线程,该线程单独一个大节来分析。
- 创建接口
- /* connect my internal structure to the blank pointer passed to me */
- *module_interface = switch_loadable_module_create_module_interface(pool, modname);
- sofia_endpoint_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_ENDPOINT_INTERFACE);
- sofia_endpoint_interface->interface_name = "sofia";
- sofia_endpoint_interface->io_routines = &sofia_io_routines;
- sofia_endpoint_interface->state_handler = &sofia_event_handlers;
- sofia_endpoint_interface->recover_callback = sofia_recover_callback;
- management_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_MANAGEMENT_INTERFACE);
- management_interface->relative_oid = "1001";
- management_interface->management_function = sofia_manage;
- SWITCH_ADD_APP(app_interface, "sofia_sla", "private sofia sla function",
- "private sofia sla function", sofia_sla_function, "<uuid>", SAF_NONE);
- 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,这个的作用还没研究到,后面研究再补充文档。
-
- 解析sip_profile
上一节的mod_sofia初始化中,有个步骤调用config_sofia(SOFIA_CONFIG_LOAD, NULL)去解析sip_profile,现在来分析下这个函数,这个函数很长,有近2000行,需要分步分析。
- switch_status_t config_sofia(sofia_config_t reload, char *profile_name)
- {
- char *cf = "sofia.conf";
- if (!(xml = switch_xml_open_cfg(cf, &cfg, params))) {
- switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", cf);
- status = SWITCH_STATUS_FALSE;
- goto done;
- }
- ...
- }
从上面可以看出,首先解析sofia.conf这个配置文件。这个配置只解析两个字段,global_settings和profiles,其中profiles很长,基本上都是在解析这个字段。在看代码的时候,可以对比autoload_configs/sofia.conf这个文件。
- if ((settings = switch_xml_child(cfg, "global_settings"))) {
- for (param = switch_xml_child(settings, "param"); param; param = param->next) {
- char *var = (char *) switch_xml_attr_soft(param, "name");
- char *val = (char *) switch_xml_attr_soft(param, "value");
- if (!strcasecmp(var, "log-level")) {
- su_log_set_level(NULL, atoi(val));
- } else if ...
- }
- if ((profiles = switch_xml_child(cfg, "profiles"))) {
- ...
- }
解析xml字段的原理比较简单,这里以解析出gobal_settings中的log-level字段为例,解析出来后调用su_log_set_level,设置协议栈的日志等级。
接下来解析很长的profile字段,这个字段在sip_profiles文件夹中的各个文件,这里以internal.xml为例。
- if ((profiles = switch_xml_child(cfg, "profiles"))) {
- for (xprofile = switch_xml_child(profiles, "profile"); xprofile; xprofile = xprofile->next) {
- char *xprofilename = (char *) switch_xml_attr_soft(xprofile, "name");
- char *xprofiledomain = (char *) switch_xml_attr(xprofile, "domain");
- if (!(settings = switch_xml_child(xprofile, "settings"))) {
- }
- }
这里基本上和配置文件对应,其中settings的解析很长,一般的配置这里就不一一分析了。
- if (!profile_already_started) {
- /* Setup the pool */
- if ((status = switch_core_new_memory_pool(&pool)) != SWITCH_STATUS_SUCCESS) {
- switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Memory Error!\n");
- sofia_profile_start_failure(NULL, xprofilename);
- goto done;
- }
- if (!(profile = (sofia_profile_t *) switch_core_alloc(pool, sizeof(*profile)))) {
- switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Memory Error!\n");
- sofia_profile_start_failure(NULL, xprofilename);
- goto done;
- }
- profile->tls_verify_policy = TPTLS_VERIFY_NONE;
- /* lib default */
- profile->tls_verify_depth = 2;
- switch_mutex_init(&profile->gw_mutex, SWITCH_MUTEX_NESTED, pool);
- profile->trans_timeout = 100;
- profile->auto_rtp_bugs = RTP_BUG_CISCO_SKIP_MARK_BIT_2833;// | RTP_BUG_SONUS_SEND_INVALID_TIMESTAMP_2833;
- profile->pool = pool;
- ...
如果没有解析过这个profile,则申请sofia_profile_t结构体内存,并初始化一些参数。
- for (param = switch_xml_child(settings, "param"); param; param = param->next) {
- char *var = (char *) switch_xml_attr_soft(param, "name");
- char *val = (char *) switch_xml_attr_soft(param, "value");
- int found = 1; // Used to break up long if/elseif chain (MSVC2015 fails (parser stack overflow) otherwise)
- switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s [%s]\n", var, val);
- if (!strcasecmp(var, "debug") && val) {
- profile->debug = atoi(val);
解析settings字段中的各个param。这里以debug为例,存到profile->debug变量。比较重要的变量有odbc_dsn,这里可以配置sip相关的数据库存到哪里。所以修改sip配置文件的odbc-dsn字段,指向mysql数据库,就会在这里赋值。
- else if (!strcasecmp(var, "odbc-dsn") && !zstr(val)) {
- profile->odbc_dsn = switch_core_strdup(profile->pool, val);
解析完所有settings参数后,会进行最后的处理。
- if (profile) {
- if (profile_already_started) {
- }
- else {
- launch_sofia_profile_thread(profile);
- }
- }
第一次启动新的线程来处理该profile的逻辑。到这里就解析完config_sofia,这里需要注意一点,sip默认有4个profile,internal、internal-ipv6、external、external-ipv6,所以会启动4条sofia_profile_thread_run线程,4个UA互不干扰。
- profile线程sofia_profile_thread_run
- void *SWITCH_THREAD_FUNC sofia_profile_thread_run(switch_thread_t *thread, void *obj)
- {
- sofia_profile_t *profile = (sofia_profile_t *) obj;
- ...
- //线程数加1
- switch_mutex_lock(mod_sofia_globals.mutex);
- mod_sofia_globals.threads++;
- switch_mutex_unlock(mod_sofia_globals.mutex);
- //跟sip协议栈相关的,
- profile->s_root = su_root_create(NULL);
- //初始化sip的数据库,创建那些表
- sofia_glue_init_sql(profile);
- //重要,这里就创建了一个UA代理,其中sofia_event_callback是很重要的回调,当sip收到协议包或状态改变,通过此回调通知应用
- do {
- profile->nua = nua_create(profile->s_root, /* Event loop */
- sofia_event_callback, /* Callback for processing events */
- profile, /* Additional data to pass to callback */
- ...
- }
- //设置一些参数
- nua_set_params(profile->nua,...);
- //创建数据库的执行队列,数据库的操作是在单独的一条线程
- switch_sql_queue_manager_init_name(qname,
- &profile->qm,
- 2,
- profile->odbc_dsn ? profile->odbc_dsn : profile->dbname,
- SWITCH_MAX_TRANS,
- profile->pre_trans_execute,
- profile->post_trans_execute,
- profile->inner_pre_trans_execute,
- profile->inner_post_trans_execute);
- switch_sql_queue_manager_start(profile->qm);
- //添加profile
- sofia_glue_add_profile(profile->name, profile);
- //创建工作线程
- worker_thread = launch_sofia_worker_thread(profile);
- //此线程只产生定时脉冲,真实工作转由工作线程
- while (mod_sofia_globals.running == 1 && sofia_test_pflag(profile, PFLAG_RUNNING) && sofia_test_pflag(profile, PFLAG_WORKER_RUNNING)) {
- su_root_step(profile->s_root, 1000);
- profile->last_root_step = switch_time_now();
- }
- //线程退出后的一些清理工作
- sofia_reg_unregister(profile);
- nua_shutdown(profile->nua);
- ...
- }
sip_profile线程干了几件重要的事:
- 调用协议栈API创建UA代理,并设置了回调sofia_event_callback,这个回调很重要,是sip通知应用的入口。
- 创建了sip数据库相关的资源、创建表、SQL队列等,数据库的操作是在单独的线程里执行的。
- 创建一条工作线程,把处理的任务转移到这条线程。
- 最后sip_profile只干定时产生时间脉冲的事。
- 工作线程sofia_profile_worker_thread_run
- void *SWITCH_THREAD_FUNC sofia_profile_worker_thread_run(switch_thread_t *thread, void *obj)
- {
- if (switch_queue_pop_timeout(mod_sofia_globals.general_event_queue, &pop, 100000) == SWITCH_STATUS_SUCCESS) {
- do {
- switch_event_t *event = (switch_event_t *) pop;
- general_event_handler(event);
- switch_event_destroy(&event);
- pop = NULL;
- switch_queue_trypop(mod_sofia_globals.general_event_queue, &pop);
- } while (pop);
- }
- ...
- }
- 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,我们单独来分析下这条线程。
- 消息处理线程sofia_msg_thread_run
- void *SWITCH_THREAD_FUNC sofia_msg_thread_run(switch_thread_t *thread, void *obj)
- {
- void *pop;
- switch_queue_t *q = (switch_queue_t *) obj;
...
- for(;;) {
- if (switch_queue_pop(q, &pop) != SWITCH_STATUS_SUCCESS) {
- switch_cond_next();
- continue;
- }
- if (pop) {
- sofia_dispatch_event_t *de = (sofia_dispatch_event_t *) pop;
- sofia_process_dispatch_event(&de);
- } else {
- break;
- }
- }
- ...
- return NULL;
- }
消息处理线程也很简单,也是从队列取出一个消息,然后调用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。