1. 程式人生 > >freeswitch源碼閱讀 之 sofia模塊

freeswitch源碼閱讀 之 sofia模塊

bug core memory bre presence func binding function spa

sofia模塊在freeswitch中的位置非常重要, 所有的sip通話都和它有關, 那麽我們就看一下該模塊的執行流程。

一、 實現的功能:

1. sip註冊;

2. 呼叫;

3. Presence;

4. SLA, 等。

二、 主要的方法, 有三個, 分別為:

[cpp] view plain copy 技術分享技術分享
  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
[cpp] view plain copy 技術分享技術分享
  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全局數據中心實例, 並一一對依變量進行初始化, 結構如下:

[cpp] view plain copy 技術分享技術分享
  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. 回調註冊的事件處理句柄,針對具體事件進行處理。

事件接收線程將事件的必要參數取出來後, 直接調用

[cpp] view plain copy 技術分享技術分享
  1. static void our_sofia_event_callback(nua_event_t event,
  2. int status,
  3. char const *phrase,
  4. nua_t *nua, sofia_profile_t *profile, nua_handle_t *nh, sofia_private_t *sofia_private, sip_t 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方法進行。

其中有針對幾張數據表的操作:

[cpp] view plain copy 技術分享技術分享
  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);
[cpp] view plain copy 技術分享技術分享
  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信息
[cpp] view plain copy 技術分享技術分享
  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狀態

[cpp] view plain copy 技術分享技術分享
  1. char *sql = switch_mprintf("update sip_dialogs set call_info_state=‘idle‘ where call_id=‘%q‘", b_call_id);

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

[cpp] view plain copy 技術分享技術分享
  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方法進入到事件選擇處理方法中。

[cpp] view plain copy 技術分享技術分享
  1. case nua_i_state:
  2. sofia_handle_sip_i_state(session, status, phrase, nua, profile, nh, sofia_private, sip, de, tags);

[cpp] view plain copy 技術分享技術分享
  1. if (r_sdp && !sofia_test_flag(tech_pvt, TFLAG_SDP)) {
  2. if (switch_channel_test_flag(channel, CF_PROXY_MODE)) {
  3. switch_channel_set_variable(channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "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. switch_channel_set_variable(channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "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. switch_channel_set_variable(channel, SWITCH_ENDPOINT_DISPOSITION_VARIABLE, "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狀態機的變化, 這就需要靠在核心狀態機上註冊相應的回調函數實現。 回調方法如下:

[cpp] view plain copy 技術分享技術分享
  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模塊申請資源的一種釋放, 主要包括線程, 內存, 及狀態值, 方法如下:

[cpp] view plain copy 技術分享技術分享
  1. SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_sofia_shutdown)
  2. {
  3. int sanity = 0;
  4. int i;
  5. switch_status_t st;
  6. switch_console_del_complete_func("::sofia::list_profiles");
  7. switch_console_set_complete("del sofia");
  8. switch_mutex_lock(mod_sofia_globals.mutex);
  9. if (mod_sofia_globals.running == 1) {
  10. mod_sofia_globals.running = 0; //改成非運行狀態
  11. }
  12. switch_mutex_unlock(mod_sofia_globals.mutex);
  13. switch_event_unbind_callback(sofia_presence_event_handler);//卸載狀態事件回調方法
  14. switch_event_unbind_callback(general_event_handler); //<span style="font-family: Arial, Helvetica, sans-serif;">卸載全局事件回調方法</span>
  15. switch_event_unbind_callback(event_handler);
  16. switch_queue_push(mod_sofia_globals.presence_queue, NULL);
  17. switch_queue_interrupt_all(mod_sofia_globals.presence_queue); //終斷所有執行POPO列隊的任務線程
  18. while (mod_sofia_globals.threads) {
  19. switch_cond_next();
  20. if (++sanity >= 60000) {
  21. break;
  22. }
  23. }
  24. for (i = 0; mod_sofia_globals.msg_queue_thread[i]; i++) {
  25. switch_queue_push(mod_sofia_globals.msg_queue, NULL);
  26. switch_queue_interrupt_all(mod_sofia_globals.msg_queue);
  27. }
  28. for (i = 0; mod_sofia_globals.msg_queue_thread[i]; i++) {
  29. switch_thread_join(&st, mod_sofia_globals.msg_queue_thread[i]);
  30. }
  31. if (mod_sofia_globals.presence_thread) {
  32. switch_thread_join(&st, mod_sofia_globals.presence_thread);
  33. }
  34. //switch_yield(1000000);
  35. su_deinit();
  36. switch_mutex_lock(mod_sofia_globals.hash_mutex);
  37. switch_core_hash_destroy(&mod_sofia_globals.profile_hash);
  38. switch_core_hash_destroy(&mod_sofia_globals.gateway_hash);
  39. switch_mutex_unlock(mod_sofia_globals.hash_mutex);
  40. return SWITCH_STATUS_SUCCESS;
  41. }


五、 分析 模塊方法的定義聲明, SWITCH_MODULE_DEFINITION, :

[cpp] view plain copy 技術分享技術分享
  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 變量中去。

freeswitch源碼閱讀 之 sofia模塊