Sofia "nua"模块--高层UA库

Sofia "nua"模块--高层UA库nua 模块包含 UA 库的实现代码 它关心 SIP UA 的基本功能 它的功能包括呼叫话务管理 消息 及事件检索 概述 NUA API 为高层应用程序提供了一个透明的 完全控制的 SIP 协议引擎 NUA 在现有的事务语义之 nta 模块的实现 上提供呼叫语义 通过 NUA 可以搭建不同的 SIP 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:

  • 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
这个版本以一个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 */

 

小讯
上一篇 2025-02-09 23:53
下一篇 2025-02-25 14:43

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/75770.html