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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章