当前位置: 首页 > 工具软件 > Sofia-SIP > 使用案例 >

Sofia-SIP辅助文档十三 - Sofia SIP用户代理库 - "nta" - SIP事务模块

印瑾瑜
2023-12-01

http://sofia-sip.sourceforge.net/refdocs/nta/index.html翻译自官网的这张网页。


模块元信息

Sofia SIP Transaction API (nta)提供针对SIP事务、传输和消息处理的简单接口。nta接口计划是网络和用户元素(need translate again)。nta的公开接口在<sofia-sip/nta.h>头文件中定义,tag参数在<sofia-sip/nta_tag.h>头文件中定义。

联系人:
Pekka Pessi < Pekka.Pessi@nokia-email.address.hidden>
状态:
Sofia SIP Core library
许可:
LGPL

NTA Objects

NTA处理很多类对象:agent (nta_agent_t),call legs (nta_leg_t),客户端外呼请求(nta_outgoing_t),呼入的服务端请求(nta_incoming_t)。

NTA使用SIP消息对象msg_tsip_t来处理消息,分别在<sofia-sip/msg.h>和<sofia-sip/sip.h>头文件中定义。SIP头都在<sofia-sip/sip.h>头文件中定义。

创建NTA代理

大部分的SIP实体,用户代理或网关,都由SIP服务器和SIP客户端协同工作组成。NTA提供nta_agent_t对象提供SIP服务器和客户端的简单接口。

调用nta_agent_create()函数将创建一个nta_agent_t对象。对象监听着传入的连接,接收消息,解析它们,并且将它们传递给应用程序。它还负责解析域名以及发送消息。

代理需要su_root_t对象来调度它自身的执行。root对象被用来等待网络事件,调度定时器函数,异步传递消息。root对象可通过su_root_create()函数创建。root对象可以有它自己的线程,或者它的主循环过程由其他应用程序线程通过调用su_root_run()函数来执行。主循环过程可以通过调用su_root_break()函数结束它。

一个简单的代理可以像如下这样来创建:

registrar->reg_root = su_root_create(NULL);

  if (registrar->reg_root) {
    registrar->reg_agent = nta_agent_create(registrar->reg_root,
                                            (url_string_t*)argv[1],
                                            NULL,
                                            NULL,
                                            NULL);

    if (registrar->reg_agent) {
      su_root_run(registrar->reg_root);
      nta_agent_destroy(registrar->reg_agent);
    }

    su_root_destroy(registrar->reg_root);
  }

SIP服务器动作

SIP服务器会响应客户端发起的事务。SIP服务器可在两种模式下运作:无状态和有状态。这一节描述一个有状态的SIP服务器如何使用NTA。

NTA腿

一条腿用来表示有状态的事务处理。腿缺省这么被创建:

default_leg = nta_leg_tcreate(agent, process_requests, context,
                               URLTAG_URL(url),
                               NTATAG_NO_DIALOG(1),
                               TAG_END());

url参数用来指定哪个URLs匹配这条腿。如果给出了url,只有那些requestURI匹配这个参数的请求才会被这条腿处理。nta_leg_tcreate() 函数也是一个tag参数函数,接收tagged参数列表。

正常的腿可被用来匹配已经存在的会话、通话或事务上下文的呼入请求,或提供有着连续头的呼出请求。当一个通话的腿被创建,From和To头会被提供给它,其他的头也可选择性地提供给它,例如Call-IDRouteCSeq

一个新的腿像这样被创建:

call_leg = nta_leg_tcreate(agent,
                            process_call_requests, call_context,
                            SIPTAG_CALL_ID(sip->sip_call_id),
                            SIPTAG_TO(sip->sip_from),
                            SIPTAG_FROM(sip->sip_to),
                            TAG_END());
注意:
在上面的例子里, FromTo头被对调了。如果头是从呼入请求中取出来的,那么这种情况会发生;当一个呼出请求初始化后 FromTo头改变方向。
一个已存在的腿可被用在任何方向上。如果一个腿为呼入的INVITE事务创建,同样也可以为呼出的BYE事务使用腿。

标记Call Leg

所有的SIP用户代理服务器都需要在它们的最终响应中标记To头。nta_leg_tag()函数可以给本地地址加上一个标记。本地地址被用来作为回复消息中的To头,就像请求中的From头一样。nta_incoming_tag()函数给呼入事务增加一个标记。它们通常一道使用,从初始响应到会话使用标记:

if (!nta_leg_tag(leg, nta_incoming_tag(irq, NULL)))
    nta_incoming_treply(irq, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());

呼入事务

针对每个唯一的呼入请求消息NTA会创建一个呼入事务对象(nta_incoming_t)。当NTA创建完这个呼入事务对象后,它会调用使用nta_leg_tcreate()函数时提供的回调函数。

最简单的响应请求的方式就是在回调函数内返回一个合法的状态码。合法的状态码范围是100至699。如果不希望自动响应,回调函数应该返回0。

注意:
如果是个最终的状态码,呼入事务对象将在回调函数返回后立即被销毁。但可以之后被重新使用。
不可能在回调函数内返回2xx的状态码给呼入的INVITE事务。

对回调函数而言合法的返回值是:

  • 0,100 .. 699,对除了INVITE的其他请求而言
  • 0,100 .. 199,300..699,对INVITE请求而言。

所有其他的返回值被解释成500,即,500 Internal Server Error响应消息被回送给客户端,请求对象立即被销毁。

简单的registrar/redirect服务器可以有一个如下所示的呼入请求回调函数:

int process_request(server_t *server,
                       nta_leg_t *leg,
                       nta_incoming_t *irq,
                       sip_t const *sip)
 {
   sip_contact_t *m;

   switch (sip->sip_request->rq_method) {
   case sip_method_register:
     return registrar_add(server, leg, reply, sip);

   case sip_method_ack:
     return 500;

   case sip_method_cancel:
     return 200;

   default:
     if (registrar_find(server, sip->sip_request->rq_url, &m) {
       nta_incoming_treply(irq, SIP_302_MOVED_TEMPORARILY,
                           SIPTAG_CONTACT(m), TAG_END());
       return 302;
     }
     else {
       nta_incoming_treply(irq, SIP_404_NOT_FOUND, TAG_END());
       return 404;
     }
   }
 }

缺省的响应消息有着缺省词组的状态行,接着是ViaToFromCall-IDCSeqContent-Length头。如果需要更复杂的响应消息,应用程序应当使用nta_incoming_treply()函数生成响应消息:

nta_incoming_treply(reply, SIP_200_OK,
                     SIPTAG_CONTACT(contact),
                     SIPTAG_CONTENT_TYPE_STR("application/sdp"),
                     SIPTAG_PAYLOAD(sdp),
                     TAG_END());

nta_incoming_treply()是个tag参数函数,接收tagged参数列表作为参数。

注意:
可以使用 nta_incoming_treply()函数发送多次暂时响应(包含1xx状态码),但只能发送一次最终响应(包含2xx..6xx状态码)。对于INVITE请求而言,一个代理可以在一个错误响应(3xx..6xx状态码)后再发送2xx最终响应。

可靠的短暂响应 - "100rel"

100rel SIP扩展提供了可靠的短暂响应,短暂的响应会持续发送直到一个特殊的确认请求PRACK收到。除了PRACK method,扩展还定义了两个头:RSeqRAck,他们用来标识不同的响应消息。PRACK method只可以在INVITE请求中使用。

使用可靠响应可以通过"100rel"选项标签进行协商。UAC(发送INVITE的一方)在SupportedRequire头内包括选项标签。第一种情况,UAC申明支持可靠响应。第二种情况,UAC要求UAS(响应一方)总是以可靠方式发送短暂响应。

当使用NTATAG_REL100() tag开启可靠响应,nta引擎在INVITE请求中自动将"100rel"选项标签插入Supported头。

可靠响应

当UAS希望可靠地响应INVITE请求,就会使用nta_reliable_treply()nta_reliable_mreply()函数,而不是nta_incoming_treply()nta_incoming_mreply()函数。这些函数返回一个指向特殊对象的指针,nta_reliable_t,被用来追踪无确认的响应,以及响应PRACK确认请求。

nta_reliable_treply ()和nta_reliable_mreply()函数均接收一个回调函数指针参数,以及一个应用程序上下文指针参数。回调函数类似于腿的回调函数。当请求收到后或超时触发回调函数被调用。

nta负责给每个可靠响应消息赋上一个序列数字,如果PRACK请求没收到那么就重发这些响应。它也会给Require头自动加上100rel选项标签。

如果一个Require头内有100rel的请求使用了通常的nta_incoming_treply()/nta_incoming_mreply()函数响应,nta代替应用程序为每个短暂响应创建一个可靠响应对象。因为应用程序可以不向nta提供PRACK回调函数,PRACK请求不会发送给应用程序。

UAC接收可靠响应

当UAC收到一个有RSeq头的暂时响应,UAC需要确认它。为了达到这个效果,需要建立一个与UAS的早期会话。换句话说,可靠响应被用来建立早期会话。UAC调用nta_leg_tcreate() 函数为这个早期会话建立一个腿对象,使用的创建参数是从响应消息继承得到的。

int invite_callback(call_t *call,
                    nta_outgoing_t *orq,
                    sip_t const *sip)
 {
   int status = sip->sip_status->st_status;

   if (!call->has_dialog &&
       (status >= 200 || (status > 100 && sip->sip_rseq))) {
     nta_leg_t *early =
       nta_leg_tcreate(call->nta_agent, mid_dialog_request, call,
                      SIPTAG_TO(sip->sip_to),
                      SIPTAG_FROM(sip->sip_from),
                      SIPTAG_CALL_ID(sip->sip_call_id),
                      SIPTAG_CSEQ(sip->sip_cseq),
                      TAG_END());

     nta_leg_client_route(early,
                         sip->sip_record_route,
                         sip->sip_contact);

     fork = call_fork(call, leg = early);

     if (!fork)�{
       handle error;
     }
     call = fork;
   }

原始的会话对象和客户端事务用来处理其他的通话分支。例如,如果宣告服务器它将不再导向完整的建立通话早期会话就将被建立,当通话完成时另一个会话会被使用。

确认可靠响应

当一个早期会话建立后,确认可靠响应是不重要的。应用程序通过调用nta_outgoing_prack()函数新建一个PRACK客户事务对象。

SIP客户端动作

SIP客户端初始化事务。SIP客户端被要求调用其他的事务,例如ACK或CANCEL,来终止初始事务。这一节描述一个SIP客户端如何使用NTA生成事务。

创建Call Leg

如果客户端没有一个合适的通话腿,它必须使用nta_leg_tcreate()函数创建它:

context->leg = nta_leg_tcreate(agent,
                                callback, context,
                                SIPTAG_CALL_ID(call_id),
                                SIPTAG_FROM(from),
                                SIPTAG_TO(to),
                                TAG_END());

呼入事务会用到回调函数和上下文指针。它们也有可能为空如果没有这样的事务。如果回调函数为空,NTA用403Forbidden响应呼入事务。

call_id可以为空,这样的话NTA会生成一个新的call ID。

from和to头用在呼出事务中。他们也被用来选择哪个到来的消息属于这个腿。

初始序列数值由SIPTAG_CSEQ()提供(接收CSeq结构体作为参数)。

使用这个腿在呼出消息中其他参数(To头之后)被包括(need translate again)。当前只支持SIPTAG_ROUTE()

注意:
其他tagged参数被忽略。

呼出请求

nta_outgoing_tcreate()用来创建和发送呼出请求:

oreq = nta_outgoing_tcreate(leg, response_to_register, reg,
                             proxy_url,
                             SIP_METHOD_REGISTER,
                             registrar_url,
                             SIPTAG_CONTACT(my_contact),
                             TAG_END());

每次收到暂时应答或最终应答,NTA会调用回调函数response_to_register()。

注意:
会有多个针对INVITE请求的最终应答。

如果NTA没有及时地收到应答,它会生成408 Timeout 响应消息,并且递给应用程序。

注意:
在针对INVITE请求的短暂应答后,NTA内不会发生超时。只要收到了任何应答,应用程序必须自己触发INVITE事务的超时。

请求可以被NTA nta_outgoing_destroy()函数销毁。如果没收到最终的应答,当请求被销毁时请求也被取消了。应用程序可以调用nta_outgoing_cancel()取消呼出请求。

确认INVITE的应答

针对INVITE请求的最终应答必须被确认。NTA负责自动确认3xx至6xx的应答;应用程序必须明确地创建针对最终2xx应答的独立确认事务。

最终应答可以像这样被确认:

url = sip->sip_contact ? sip->sip_contact->m_url : original_url;
  ack = nta_outgoing_tcreate(leg, NULL, NULL,
                             SIP_METHOD_ACK,
                             (url_string_t*)url,
                             SIPTAG_CSEQ(sip->sip_cseq),
                             SIPTAG_PAYLOAD(sdp),
                             TAG_END());
注意:
ACK事务应当发送给2xx回复中的Contact。

消息的无状态处理

当一个NTA代理被创建,可以向其提供一个无状态回调函数。当收到的SIP请求或响应消息无法匹配到任何一个已有的事务,回调函数将会被调用。

在调用无状态回调函数前,代理会试图将收到的请求消息与已有的会话和无会话腿做匹配(或缺省腿)。因此,如果你创建一个缺省腿,所有的请求消息会被有状态地处理,而不是交给无状态函数处理。

如果你希望用无状态函数处理请求消息,仍然使用无会话腿(例如,用nta_leg_by_uri()为了查询域名),你不得不在nta_agent_create()或

nta_agent_set_params()函数内使用NTATAG_STATELESS(1)转换到无状态模式。

如果一个响应消息未匹配到任何一个已有的客户端事务,代理将试图使用缺省呼出事务。如果你已经创建了一个缺省呼出事务,所有迷路的响应消息会提供给它而不是无状态处理函数。

无状态回调函数

除了消息(msg)和解析过的内容(sip)回调函数还有应用程序特定的上下文指针(这个例子registrar)以及指向NTA代理的指针(agent)这两个参数:

int process_message(nta_agent_context_t *registrar,
                       nta_agent_t *agent,
                       msg_t *msg,
                       sip_t *sip);

应用程序在无状态模式下有三个函数可以处理消息:

另外,使用nta_incoming_create()函数可以有状态地处理请求消息。

无状态回调函数的功能可以差别很大,依赖于应用程序的功效。用户代理、网关或者注册服务器/重定向服务器,每类都可以有非常不一样的回调函数。

一个简单的重定向服务器可以有一个类似于下面所示的消息回调函数。

int process_message(redirect_t *r,
                       nta_agent_t *agent,
                       msg_t *msg,
                       sip_t *sip)
 {
   sip_contact_t *m;
   sip_unsupported_t *u;

进入的响应消息只是简单地被忽略。ACK请求可以安全地丢弃,因为重定向服务器保持无状态。

if (!sip->sip_request || sip->sip_request->rq_method == sip_method_ack) {
     nta_msg_discard(agent, msg);
     return 0;
   }

下一步重定向服务器首先检查正在处理的请求是否需要一个它不支持的特性:

u = sip_unsupported(msg_home(msg), sip->sip_require, r->r_supported);
   if (u) {
     nta_msg_treply(agent, msg, SIP_420_BAD_EXTENSION,
                    SIPTAG_SUPPORTED(r->r_supported),
                    SIPTAG_UNSUPPORTED(u),
                    TAG_END());
     return 0;
   }

CANCEL请求终止一个事务。无状态重定向服务器没有事务,因此它用481 Call Leg/Transaction Does Not Exist消息重定向回复:

if (sip->sip_request->rq_method == sip_method_cancel) {
     nta_msg_treply(agent, msg, SIP_481_NO_TRANSACTION, TAG_END());
     return 0;
   }

所有其他请求正常情况下会被302响应应答。搜索定位服务查找请求的uri,如果一个匹配地址被找到了,一个活动绑定列表返回给客户端。

m = location_find(redirect, sip->sip_request->rq_url);
   if (m) {
     nta_msg_treply(agent, msg, SIP_302_MOVED_TEMPORARILY,
                    SIPTAG_CONTACT(m),
                    TAG_END());
   }

否则,404 Not Found会发送:

else {
     nta_msg_treply(agent, msg, SIP_404_NOT_FOUND, TAG_END());
   }

   return 0;
 }

 类似资料: