Sofia "nua"模塊--高層UA庫

         nua 模塊包含UA庫的實現代碼,它關心SIP UA的基本功能。它的功能包括呼叫話務管理、消息,及事件檢索。

概述

        NUA API爲高層應用程序提供了一個透明的,完全控制的SIP協議引擎。NUA在現有的事務語義之(nta模塊的實現)上提供呼叫語義。通過NUA可以搭建不同的SIP UA,比如說終端、網關,或MCU。

        nua 引擎爲應用程序員隱藏了許多底層信令和媒體管理細節。它可以使用不同的媒體接口,甚至可以是遠程的,完全透明的方式處理媒體。

        UA庫內的協議引擎和應用程序可以運行在不同的線程中。協議引擎與應用層線程通過事件(events)機制通信,通過回調函數傳遞給應用層。回調函數由應用層的上下文相關線程調用,通過一個su_root_t 對象體現。

 

NUA用戶的Sofia 概念 

引言

        Sofia 軟件套件是基於目前的基礎觀點和概念建立的,這些概念在Sofia軟件的所有層級中通用。裏面的許多內容都由Sofia工具庫(su)實現,並對多數主流OS提供了統一的接口封裝。

        這面章節介紹這些概念,NUA用戶必須理解它們才能讓Sofia程序正常工作。其它工具(SU庫提供的,或Sofia 軟件套件的其它庫提供的)可能對應用程序開發者也是有幫助的,但是使用時務必小心,因爲它們有可能改變Sofia軟件套件的行爲,從而導致NUA庫工作異常。更多細節可以參考[su]模塊的相關文檔。

 

事件循環Event loop 與根對象 root object

        NUA以事件反應器模式(也稱爲分發及通知模式)驅動事件系統(請參考[Using Design Patterns to Develop Reusable Object-oriented Communication Software, D.C. Schmidt, CACM October '95, 38(10): 65-74]一書)。Sofia以任務作爲編程模型的基本執行單元。根據編程模型,程序可以請求事件循環在特定事件觸發時調用回調函數。具體事件包括I/O激活,定時器或其它任務傳遞的異步消息。

        root 對象是應用軟件中描述一個任務的句柄。透視事件的另一種方式是:root對象描述任務的主事件循環。通過root對象,任務代碼可以訪問它的上下文信息(magic)和線程同步,比如說等待對象、定時器,消息。

        使用NUA服務的應用必須創建一個root對象,並設置處理NUA事件的回調函數。調用 su_root_create() 創建root對象,調用nua_create() 函數註冊回調函數。root對象的數據類型是su_root_t

 

Magic

        magic 是一種描述上下文指針的術語,應用程序可以把它綁定到Sofia棧的各種對象上(比如說root對象和操作句柄)。當主事件循環調用註冊的回調函數時,上下文指針會回傳給應用層代碼。Sofia棧保留回調函數調用之間的上下文信息。應用層可以利用上下文信息存儲處理事件所需要的任何信息。

內存處理

        給定的任務需要分配許多小塊內存時,使用home-based內存管理機制會很便利。內存通過home對象分配,它維護所有內存塊的引用信息。當home對象釋放時,所有相關內存一併釋放。這個機制簡化了應用邏輯,因爲應用層代碼不需要跟蹤內存塊的分配記錄,也不需要獨立釋放每一個內存塊。

        使用NUA服務的應用程序可以使用SU庫提供的內存管理服務,但這不是強制要求的。

Tags

        Tagging 是Sofia軟件所使用的一種機制,它用於向函數打包參數。它可以傳遞非固定類型的可變長度的參數。對應用層程序員來說,tagging是一個可見的宏,可用它封裝傳遞的參數。評估時,tagging宏創建一個包含tag的結構(記錄參數的類型),和一個值(指向不透明數據)。通過檢查tag,Sofia軟件的各層可以檢查它們是否能夠處理參數,或者簡單地把它傳遞給下一層處理。

        有一些具有特殊含義的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
這個版本以一個C語言字符串作爲參數。不帶_STR 後綴的相應tag將解析的值結構作爲參數。

        下面是一個調用實例:

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消息的過程。

 

調試與日誌

       Sofia 棧的模塊包含可配置的調試及日誌功能,它們的服務接口在<sofia-sip/su_log.h>裏定義。調試及日誌細節(比如說日誌等級,輸出文件名)可以通過環境變量配置,或者通過配置文件指定,還有代碼層的指定。

定義的環境變量及含義:

  • 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棧提供的調試和日誌服務,但不強求。

 

NUA Concepts

NUA 棧對象

        棧對象描述SIP棧和媒體引擎的實例。它包含指向棧root對象的引用,UA指定的設置,還有SIP事務引擎的引用。

        NUA棧對象調用nua_create() 創建,調用nua_destroy()銷燬。nua 引擎可以用nua_shutdown() 函數優雅地釋放會話。

        NUA棧對象的類型爲nua_t。

NUA 操作句柄

        操作句柄描述一個抽象的SIP call/session。它包含SIP dialog的信息和媒體session,呼叫的狀態機,高層SDPoffer-answer協議,註冊,訂閱,發佈和簡單的SIP事務。操作句柄還可能包含用於NUA構建SIP消息的tag列表,比如說From和To頭域。

        應用程序用NUA顯式創建操作句柄來發送消息(nua_handle()函數),或者棧處理收入呼時隱式創建(INVITE 或MESSAGE)。句柄通過NUA調用銷燬(nua_handle_destroy())。

        指示及響應事件與操作句柄關聯。

        NUA操作句柄的類型是nua_handle_t。

棧線程及消息傳遞概念

        棧線程與應用程序分離,它提供實時的協議棧操作,因此應用程序線程可以自己堵塞處理,比如重繪UI。

        棧線程與應用線程之間的通信是異步的。很多NUA API函數都會發消息給棧線程處理,類似的,某些事件發生後,棧線程也會嚮應用線程發消息。當應用程序調用su_root_run() 或 su_root_step()函數時,嚮應用線程發的消息作爲它的回調進行傳遞。

SIP 消息和頭域處理

        SIP消息通過類型安全的SIPTAG_ tag處理。每個SIPtag有三種版本:

  • SIPTAG_<tagname>() 以解析後的值作爲參數。
  • SIPTAG_<tagname>_STR() 以未解析的字符串爲參數
  • SIPTAG_<tagname>_REF() 以引用爲參數,與tl_gets() 函數一同使用,以從tag列表中檢索tag值。
  • SIPTAG_<tagname>__STR_REF() 以引用爲參數,與tl_gets() 函數一同使用,以從tag列表中檢索字符串tag值。

For example a header named "Example" would have tags names SIPTAG_EXAMPLE(), SIPTAG_EXAMPLE_STR(), and SIPTAG_EXAMPLE_REF().

        比如說,有個頭域名爲"Example",它將擁有這幾個tag:SIPTAG_EXAMPLE(), SIPTAG_EXAMPLE_STR(), 和SIPTAG_EXAMPLE_REF()。

        當在NUA呼叫中使用tag時,會將相應的頭域添加到消息中。如果頭域只能在消息中出現一次,並且消息中已經存在一個相應的頭域,那麼tag提供的值取代現有的頭域值。傳遞tag值NULL對頭域沒有影響。傳遞tag值 (void *)-1將相應的頭域從消息中移除。

例如:

  • 發送一條帶有一個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棧的基本使用場景。

外呼

SIP_outgoing_call.gif

入呼

SIP_incoming_call.gif

基本外發操作

SIP_basic_outgoing_operation.gif

基本消息接收操作

SIP_basic_incoming_operation.gif

帶鑑權的外發操作

SIP_outgoing_operation_with_auth.gif

簡單應用實例

        接下來,將以代碼形式給出一個實例,展示如何利用NUA服務構建應用。實例代碼並不完整,但能夠體現NUA使用的所有相關細節。

        在sourceforge上有一個可用的實例程序 sofisip_cli.c ,可以作爲更完整的示例研究。

數據結構定義

        使用NUA提供服務的程序通常定義一些用於存儲上下文信息(比如說"magic")的數據域。指向這些上下文信息數據域的指針通過類型定義傳遞給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棧對象和句柄對應用層程序員來說是不透明的。同樣,應用層的上下文對NUA棧模塊也是完全不透明的。NUA函數傳入一個指針,然後這個指針在回調函數的參數中回傳給應用層。在這種場景下,應用層上下文信息結構還可以存儲root對象和home內存基址。此外,應用層上下文信息中還包含NUA棧對象信息。

初始化與清除

        以下代碼展示應用層初始化系統,進入主循環處理消息,消息 處理結束,系統清除的過程。

        如果應用程序不僅響應收到的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棧註冊事件的相關回調函數,NUA棧捕獲事件時,就會調用回調函數處理事件。回調函數的內容是一組最簡單的 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呼叫是怎樣發起的。

        place_a_call()函數創建一個句柄並調用SIP 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 */

       app_r_invite() 函數在收到INVITE消息的應答時,由協議棧的回調函數調用。這裏假設沒有激活automatic acknowledge,因此必須顯式地發送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 event事件,回調函數調用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呼叫是如何終止的。

       erminate_call() 函數發出SIP BYE消息。

void terminate_call(void)
{
  nua_bye(op->handle, TAG_END());

} /* terminate call */

 

        在收到BYE消息的應答消息時,回調函數調用app_r_bye()函數。它銷燬呼叫句柄並釋放存儲操作上下文信息的相關內存。

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()函數。它銷燬呼叫句柄並釋放存儲操作上下文信息的相關內存。

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 Server

...
  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。app_callback函數的狀態及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棧的工作的。

        shutdown()函數開始關停操作:

void shutdown(void)
{
  nua_shutdown(appl->nua);

} /* shutdown */

 

        無論NUA棧的停止是成功或失敗,回調函數調用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 */

 

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