http://sofia-sip.sourceforge.net/refdocs/nua/index.html,翻譯自官網的這張網頁。
模塊元信息
nua模塊是一個用戶代理庫,主要提供基本的SIP用戶代理功能。它的功能包括呼叫管理,消息和事件獲取。
- 聯繫人:
- Pekka Pessi <[email protected]>
- 狀態:
- Sofia SIP Core library
- 許可:
- LGPL
- 貢獻者:
-
- Pekka Pessi <[email protected]>
- Pasi Rinne-Rahkola <[email protected]>
- Kai Vehmanen <[email protected]>
- Martti Mela <[email protected]>
概述
The NUA API提供給高層應用開發人員一套透明並且完全控制的底層SIP協議引擎。NUA提供的呼叫語義基於nta模塊內的事務語義。使用NUA可以創建各類不同的SIP用戶代理,例如終端、網關或者MCU。
nua引擎對開發人員而言隱藏了很多底層的信令和媒體管理操作。在一種完全透明的方式下可以使用各類媒體接口。
應用程序和用戶代理庫內的協議引擎可以分處不同的線程內。協議引擎和應用程序使用事件進行通信,主要通過應用程序提供的回調函數。回調函數在應用程序線程上下文環境下被調用,會向其傳遞su_root_t對象。
NUA User下的Sofia概念
介紹
Sofia軟件包基於一些特定基本的觀點和概念。它們中的很多在Sofia工具庫(su)內實現,並且針對最重要的操作功能和工具提供統一的接口。
下面這些章節包括創建一個基於NUA庫的可工作應用程序的所有概念的描述。對應用程序開發人員而言SU庫內以及Sofia軟件包其他庫內的工具接口都非常有用,但開發人員必須非常小心地使用它們,因爲不正確地使用NUA庫將會改變Sofia軟件包的行爲。請至su庫查看更多的SU功能細節描述。
事件循環 - root對象
The NUA爲事件驅動系統使用了反應器模式(也可以稱爲發佈器模式或通知者模式)。Sofia在編程模型中採用了任務作爲基本的執行單元。程序可以要求當一個特定的事件發生時,事件循環調用一個回調函數。事件可以是IO操作,計時器,或者其他任務發來的異步消息。
在應用程序中root對象是一個代表任務的句柄。同樣,root對象還可以代表任務的主事件循環。通過root對象,任務代碼可訪問到它自身的上下文信息以及線程同步特性對象,例如等待對象、定時器和消息。
使用NUA功能的應用程序創建一個root對象,以及一個處理NUA事件的回調函數。可以用su_root_create()函數創建root對象,在nua_create()函數中註冊回調函數。
root對象的類型是su_root_t。
請查看<sofia-sip/su_wait.h>和<su_root.c>兩個源代碼文件獲得root對象的更多信息。
請查看nua_event_e章節獲得更多的回調函數信息:http://sofia-sip.sourceforge.net/refdocs/nua/nua_8h.html#abca36033336ce16e538a279413d2ca51。
Magic
magic是可以綁定到任何對象的上下文指針術語。主事件循環負責調用回調函數,並且將這個指針傳遞給回調函數。Sofia棧維持這個上下文信息在通話和回調函數之間。應用程序可以使用上下文用來保存任何需要的信息。
內存處理
當爲給定的任務分配很多內存塊時,home-based內存管理非常有用。所有的內存分配均通過home內存,它維護着所有已分配內存的參考。當home內存被釋放時,它將釋放所有它參考的內存塊。這將簡化應用程序處理邏輯,因爲應用程序不再需要保持監視每個已分配的內存塊、不再需要再單獨釋放每塊已分配的內存塊。
使用NUA功能的應用程序可以使用su庫提供的內存管理功能,但不使用這個功能也可以。
請參考<sofia-sip/su_alloc.h>文檔以獲得更多內存管理功能方面的信息。
Tags
Tagging是Sofia軟件包中的一項將參數打包給函數的機制。它允許將具備不固定類型的可變個數參數傳遞給函數。對應用程序開發人員而言,tagging以宏的形式出現用來封裝參數。展開tagging宏後將得到一個包含tag(說明參數的類型)和值(不透明數據的指針)的結構體。Sofia軟件包內的各個層檢查是否該由他們自身來處理這些參數還是應當傳遞給更低一層處理。
下面這些tag有着特殊的含義:
- TAG_NULL() (與TAG_END()含義一致) tag列表的尾端
- TAG_SKIP() 空tag元素
- TAG_NEXT() 指向另一個tag列表的tag元素,並且結束當前tag列表
- TAG_ANY() 可接收任意tag的過濾器tag
- TAG_IF() 條件tag元素
可向NUA函數提供tag值列表,但在參數列表的尾端必須如下圖所示的參數:
tag_type_t tag, tag_value_t value, ...);
參數列表中最後一個tag值必須是TAG_NULL()(或者TAG_END(),與TAG_NULL()一致)。
每個tag有兩個版本:
NUTAG_<tagname>
它接受一個值參數
NUTAG_<tagname>_REF
它接受一個引用參數。後者與tl_gets()函數一塊使用可以獲取tag列表內的tag值。
對SIP頭來說,還有額外的tag版本:
SIPTAG_<tagname>_STR
這個tag版本接受C-語言類型的字符串作爲參數。相應無_STR後綴版本接受解析過的結構體作爲參數。
下面是一個包含tag值的NUA函數調用示例:
nua_unregister(op->op_handle, TAG_IF(use_registrar, NUTAG_REGISTRAR(registrar)), SIPTAG_CONTACT_STR("*"), SIPTAG_EXPIRES_STR("0"), TAG_NULL());
使用NUA功能的應用程序必須使用tag機制向函數傳遞參數。請參考nua_invite()函數的說明獲得更多如何通過tag機制生成SIP消息的內容:http://sofia-sip.sourceforge.net/refdocs/nua/nua_8h.html#a8162fd7f0f1c693f2d49bb18f36acf52。
請參考<sofia-sip/su_tag.h>源代碼文件獲得更多tag方面的信息,和Sofia軟件包內各個模塊內針對本模塊特定的tag信息。
調試和日誌
Sofia軟件包具備可配置的調試和日誌功能,基於頭文件中定義的功能。調試和日誌細節(例如輸出內容等級以及輸出文件名)可通過環境變量、配置文件中的指令以及源代碼文件中的編譯指令決定。
指令和環境變量包括:
- SOFIA_DEBUG 缺省調試等級(0..9)
- NUA_DEBUG NUA調試等級(0..9)
- NTA_DEBUG 事務引擎調試等級(0..9)
- TPORT_DEBUG 傳輸事件調試等級(0..9)
- TPORT_LOG 如果設置了,打印所有傳輸層使用的解析過的SIP消息
- TPORT_DUMP 保存傳輸層中所有未解析消息的轉儲文件名
預定義的調試輸出等級:
- 0 非常嚴重的錯誤
- 1 嚴重錯誤,子系統層次最簡化進度消息
- 2 非嚴重錯誤
- 3 告警,進度消息
- 5 信令協議動作(呼入包...)
- 7 媒體協議動作(呼入包...)
- 9 進入/退出函數,極細緻的進度消息
使用NUA功能的應用程序也可以使用Sofia軟件包提供的調試和日誌服務。但不使用調試和日誌服務也可以。
請查看<sofia-sip/su_log.h>頭文件獲得更多的調試和日誌功能。
NUA概念
NUA Stack對象
Stack對象表示一個SIP stack和媒體引擎實例。它包括stack根對象的引用,用戶代理特殊配置,以及SIP事務引擎的引用。
nua_create()函數用來創建一個NUA stack對象,nua_destroy() 函數刪除它。nua_shutdown()函數優雅地釋放nua引擎維護着的活動狀態的會話。
NUA stack對象的類型是nua_t。
NUA Operation Handle操作句柄
Operation handle表示一個抽象的SIP call/會話。它包括SIP對話和媒體會話的信息,call的狀態機,高層SDP offer-answer協議,註冊,訂閱,發佈,和簡單的SIP事務。Operation handle也可以包含NUA創建的SIP消息中使用的tags列表。
一個operation handle由應用程序通過函數nua_handle()顯示創建用來發送消息,或由棧在收到INVITE或MESSAGE消息後創建。應用程序通過調用nua_handle_destroy()函數刪除它。
指示或響應事件與某一個特定的operation handle相關。
NUA operation handle的類型是nua_handle_t。
棧線程和消息傳遞
棧線程與應用程序線程是分開的,它提供了實時的協議棧操作以便應用程序線程可以做其它事情。
棧線程和應用程序線程之間的通信是異步方式的。大部分的NUA API將引起一條發給棧線程處理的消息,同樣當棧線程發生了一些事它將嚮應用程序線程發送一條消息。提供給應用程序線程的消息將通過回調函數發送,回調函數是在應用程序調用su_root_run()或su_root_step()函數時提供的。
SIP消息和頭操作
使用SIPTAG_ tags操作SIP消息。每個SIP tag有三個版本:
- SIPTAG_<tagname>()接受解析過後的值作爲參數。
- SIPTAG_<tagname>_STR()接受未解析的字串作爲參數。
- SIPTAG_<tagname>_REF()接受一個引用作爲參數,與tl_gets()函數一道使用從tag列表內取出tag值。
- SIPTAG_<tagname>__STR_REF()接受一個引用作爲參數,與tl_gets()函數一道使用從tag列表內取出字串tag值。
例如一個名爲Example的頭,它會有如下這些tags:SIPTAG_EXAMPLE()、SIPTAG_EXAMPLE_STR()和SIPTAG_EXAMPLE_REF()。
當在NUA的函數中使用tags,一個對應的頭會寫入消息體內。如果在一個消息體內一個頭只允許出現一次,最後給出的tags的值會替換已有的tags的值。傳遞一個空的tags值不會有任何作用。傳遞(void *)-1 tag值將使得從消息體內移出響應的頭。
例如:
- 發送一個有Event:頭和兩個Accept:頭的SUBSCRIBE消息:
nua_subscribe(nh, SIPTAG_EVENT_STR("presence"), SIPTAG_ACCEPT(accept1), SIPTAG_ACCEPT(accept2), TAG_END());
- 處理nua_r_subscribe消息時獲取tag值:
sip_accept_t *ac = NULL; sip_event_t *o = NULL; tl_gets(tl, SIPTAG_EVENT_REF(o), /* _REF takes a reference! */ SIPTAG_ACCEPT_REF(ac), TAG_END());
SIP/NUA入門
這一節用消息序列圖的方式描述了NUA/Sofia棧的基本使用場景。
Outgoing Call
Incoming Call
Basic Outgoing Operation
Basic Incoming Operation
Outgoing Operation with Authentication
簡單的應用程序
接下來的章節將用一個簡單應用程序的代碼展示如何使用NUA功能。展示的不是完整例子,但給出了使用NUA的所有相關細節。
在sourceforge.net網站上有一個完整的例子程序sofisip_cli.c,它可以作爲學習用。
數據結構和定義
使用NUA功能的應用程序通常會定義一個存放上下文信息的數據空間。這些上下文信息的空間的指針類型會傳給NUA。
/* type for application context data */ typedef struct application application; #define NUA_MAGIC_T application /* type for operation context data */ typedef union oper_ctx_u oper_ctx_t; #define NUA_HMAGIC_T oper_ctx_t
信息空間內容本身可用C結構體或聯合體定義:
/* example of application context information structure */ typedef struct application { su_home_t home[1]; /* memory home */ su_root_t *root; /* root object */ nua_t *nua; /* NUA stack object */ /* other data as needed ... */ } application; /* Example of operation handle context information structure */ typedef union operation { nua_handle_t *handle; /* operation handle / struct { nua_handle_t *handle; /* operation handle / ... /* call-related information */ } call; struct { nua_handle_t *handle; /* operation handle / ... /* subscription-related information */ } subscription; /* other data as needed ... */ } operation;
NUA stack對象和句柄對應用程序開發人員而言是不透明的。同樣,應用程序上下文也是完全對NUA stack模塊透明的。傳遞給NUA函數的是一些指針,在後續這個指針會返回給應用程序提供的回調函數。在這個例子中應用程序上下文信息結構體還用來保存root對象和memory home對象。同時,還保存了NUA stack對象。
初始化和退出前清除
下面的代碼展示了一個應用程序中初始化系統、進入處理消息的主循環以及主循環結束後清理系統階段。
如果應用程序不只是響應接收到的SIP消息,它還想向NUA發送消息。這可以通過在另一線程內調用NUA函數來發送消息,或者應用程序有一個socket連接用來接收命令(請查看su_wait_create()和su_root_register()函數的文檔)。
/* Application context structure */ application appl[1] = {{{{(sizeof appl)}}}}; /* initialize system utilities */ su_init(); /* initialize memory handling */ su_home_init(appl->home); /* initialize root object */ appl->root = su_root_create(appl); if (appl->root != NULL) { /* create NUA stack */ appl->nua = nua_create(appl->root, app_callback, appl, /* tags as necessary ...*/ TAG_NULL()); if (appl->nua != NULL) { /* set necessary parameters */ nua_set_params(appl->nua, /* tags as necessary ... */ TAG_NULL()); /* enter main loop for processing of messages */ su_root_run(appl->root); /* destroy NUA stack */ nua_destroy(appl->nua); } /* deinit root object */ su_root_destroy(appl->root); appl->root = NULL; } /* deinitialize memory handling */ su_home_deinit(appl->home); /* deinitialize system utilities */ su_deinit();
處理事件
在應用程序初始化階段提供給nua_create()函數的回調函數,將用來處理從NUA stack得到的事件。回調函數的模式很簡單,就是一個大型的switch/case語句,然後提供給不同的函數處理特定的消息。
void app_callback(nua_event_t event, int status, char const *phrase, nua_t *nua, nua_magic_t *magic, nua_handle_t *nh, nua_hmagic_t *hmagic, sip_t const *sip, tagi_t tags[]) { switch (event) { case nua_i_invite: app_i_invite(status, phrase, nua, magic, nh, hmagic, sip, tags); break; case nua_r_invite: app_r_invite(status, phrase, nua, magic, nh, hmagic, sip, tags); break; /* and so on ... */ default: /* unknown event -> print out error message */ if (status > 100) { printf("unknown event %d: %03d %s\n", event, status, phrase); } else { printf("unknown event %d\n", event); } tl_print(stdout, "", tags); break; } } /* app_callback */
發起通話
下面三個函數展示一個基本的SIP通話如何被創建。
The place_a_call()函數創建了一個operation handle並且調用nua_invite函數。
operation *place_a_call(char const *name, url_t const *url) { operation *op; sip_to_t *to; /* create operation context information */ op = su_zalloc(appl->home, (sizeof *op)); if (!op) return NULL; /* Destination address */ to = sip_to_create(NULL, url); if (!to) return NULL; to->a_display = name; /* create operation handle */ op->handle = nua_handle(appl->nua, op, SIPTAG_TO(to), TAG_END()); if (op->handle == NULL) { printf("cannot create operation handle\n"); return NULL; } nua_invite(op->handle, /* other tags as needed ... */ TAG_END()); } /* place_a_call */
當INVITE消息的響應消息收到後,app_r_invite()函數會被回調函數調用。這裏假定自動回覆確認被禁用了因此ACK響應消息必須顯式發送。
void app_r_invite(int status, char const *phrase, nua_t *nua, nua_magic_t *magic, nua_handle_t *nh, nua_hmagic_t *hmagic, sip_t const *sip, tagi_t tags[]) { if (status == 200) { nua_ack(nh, TAG_END()); } else { printf("response to INVITE: %03d %s\n", status, phrase); } } /* app_r_invite */
當通話狀態改變後,nua_i_state事件會被髮送(app_i_state()函數由回調函數調用)。
void app_i_state(int status, char const *phrase, nua_t *nua, nua_magic_t *magic, nua_handle_t *nh, nua_hmagic_t *hmagic, sip_t const *sip, tagi_t tags[]) { nua_callstate_t state = nua_callstate_init; tl_gets(tags, NUTAG_CALLSTATE_REF(state), NUTAG__REF(state), state = (nua_callstate_t)t->t_value; printf("call %s\n", nua_callstate_name(state)); } /* app_i_state */
接聽通話
當INVITE消息收到後,app_i_invite()函數會被回調函數調用。這個例子假定自動應答被禁用了因此響應消息必須顯式發送。
void app_i_invite(int status, char const *phrase, nua_t *nua, nua_magic_t *magic, nua_handle_t *nh, nua_hmagic_t *hmagic, sip_t const *sip, tagi_t tags[]) { printf("incoming call\n"); nua_respond(nh, 200, "OK", SOA_USER_SDP(magic->sdp), TAG_END()); } /* app_i_invite */
當通話成功建立且媒體被激活,app_i_state()函數會被回調函數調用。
void app_i_active(int status, char const *phrase, nua_t *nua, nua_magic_t *magic, nua_handle_t *nh, nua_hmagic_t *hmagic, sip_t const *sip, tagi_t tags[]) { printf("call active\n"); } /* app_i_active */
結束通話
下面這三個函數展示一個基本SIP通話是如何被終止的。
terminate_call()函數會發送一個BYE消息。
當BYE消息的響應收到後,app_r_bye()函數會被回調函數調用。函數將銷燬通話句柄並且釋放分配給operation上下文信息的空間。
void app_r_bye(int status, char const *phrase, nua_t *nua, nua_magic_t *magic, nua_handle_t *nh, nua_hmagic_t *hmagic, sip_t const *sip, tagi_t tags[]) { if (status < 200) return; printf("call released\n"); /* release operation handle */ nua_handle_destroy(hmagic->handle); op->handle = NULL; /* release operation context information */ su_free(appl->home, hmagic); } /* app_r_bye */
當BYE消息收到後,app_i_bye()函數會被回調函數調用。函數將銷燬通話句柄並且釋放分配給operation上下文信息的空間。
void app_i_bye(int status, char const *phrase, nua_t *nua, nua_magic_t *magic, nua_handle_t *nh, nua_hmagic_t *hmagic, sip_t const *sip, tagi_t tags[]) { printf("call released\n"); /* release operation handle */ nua_handle_destroy(hmagic->handle); op->handle = NULL; /* release operation context information */ su_free(appl->home, hmagic); } /* app_i_bye */
發送一條消息
下面函數將展示一個SIP MESSAGE消息是如何被髮送的。
send_message()函數會發送SIP MESSAGE消息。
void send_message(void) { op_t *op; /* create operation context information */ op = su_zalloc(appl->home, sizeof(op_t)); if (op = NULL) { printf("cannot create operation context information\n"); return; } /* how we create destination_address? */ /* create operation handle */ op->handle = nua_handle(appl->nua, op, NUTAG_URL(destination_address), TAG_END()); if (op->handle == NULL) { printf("cannot create operation handle\n"); return; } /* send MESSAGE */ nua_message(op->handle, SIPTAG_CONTENT_TYPE_STR("text/plain"), SIPTAG_PAYLOAD_STR("Hello, world!"), /* other tags as needed ... */ TAG_END()); } /* send_message */
當MESSAGE消息的響應消息收到後,app_r_message()函數會被回調函數調用。
void app_r_message(int status, char const *phrase, nua_t *nua, nua_magic_t *magic, nua_handle_t *nh, nua_hmagic_t *hmagic, sip_t const *sip, tagi_t tags[]) { printf("response to MESSAGE: %03d %s\n", status, phrase); } /* app_r_message */
接收一條消息
下面函數將展示一個SIP MESSAGE消息如何被接收。
當一個SIP MESSAGE收到後,app_i_message()函數會被回調函數調用。
void app_i_message(int status, char const *phrase, nua_t *nua, nua_magic_t *magic, nua_handle_t *nh, nua_hmagic_t *hmagic, sip_t const *sip, tagi_t tags[]) { printf("received MESSAGE: %03d %s\n", status, phrase); printf("From: %s%s" URL_PRINT_FORMAT "\n", sip->sip_from->a_display ? sip->sip_from->a_display : "", sip->sip_from->a_display ? " " : "", URL_PRINT_ARGS(sip->sip_from->a_url)); if (sip->sip_subject) { printf("Subject: %s\n", sip->sip_subject->g_value); } if (sip->sip_payload) { fwrite(sip->sip_payload->pl_data, sip->sip_payload->pl_len, 1, stdout); fputs("\n", stdout); } } /* app_i_message */
創建一個Presence服務器
... application_t *app; operation_t *oper; ... oper->app = app; app->nua = nua_create(ssip->s_root, app_callback, app, TAG_NULL()); ... oper->handle = nua_handle(app->nua, app, NUTAG_URL(to->a_url), SIPTAG_TO(to), ta_tags(ta)); ... nua_notifier(oper->handle, SIPTAG_EXPIRES_STR("3600"), SIPTAG_EVENT_STR("presence"), SIPTAG_CONTENT_TYPE_STR("application/pidf-partial+xml"), NUTAG_SUBSTATE(nua_substate_pending), TAG_END());
nua_notifier對象(presence server)創建後,nua_r_notifier事件會返回。回調函數中的status和phrase兩個參數值會指出創建成功了。
一個訂閱請求的授權會在回調函數中處理。
void app_callback(nua_event_t event, int status, char const *phrase, nua_t *nua, application_t *app, nua_handle_t *nh, oper_t *op, sip_t const *sip, tagi_t tags[]) { nea_sub_t *subscriber = NULL; switch (event) { case nua_i_subscription: tl_gets(tags, NEATAG_SUB_REF(subscriber), TAG_END()); nua_authorize(nua_substate_active); default: break; }
關閉
下面函數將展示一個應用程序如何關閉NUA stack。
shutdown()函數開始這麼一個終止過程。
void shutdown(void) { nua_shutdown(appl->nua); } /* shutdown */
當NUA stack終止結束或失敗都回觸發回調函數調用app_r_shutdown()函數。
void app_r_shutdown(int status, char const *phrase, nua_t *nua, nua_magic_t *magic, nua_handle_t *nh, nua_hmagic_t *hmagic, sip_t const *sip, tagi_t tags[]) { printf("shutdown: %d %s\n", status, phrase); if (status < 200) { /* shutdown in progress -> return */ return; } /* end the event loop. su_root_run() will return */ su_root_break(magic->root); } /* app_r_shutdown */