- 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。