Chapter 10:Basic User Agent Layer (UA)
10.1 Basic Dialog Concept
基本的UA对话( UA dialog)提供了管理SIP对话和对话实例( dialog usage)的基本工具,如基本的对话状态,会话计数器, Call-ID, From, To和 Contact头, 事务中CSeq的序列,route-set。
基本的UA对话使用了哪种会话类型是不可知的(如 INVITE session, SUBSCRIBE/NOTIFY sessions, REFER/NOTIFY sessions),在一个单独的对话中可以建立多种不同的会话类型。
一个PJSIP对话可以被认为是一个被动的数据结构用来保存通用的对话属性。不可以将对话和INVITE会话弄混。一个INVITE会话( 通常叫做dialog usage)是对话中的一个会话。在同一个对话中也可以有其他的会话/用法;它们共享通用的对话属性(尽管每个对话里只有一个INVITE会话)。
PJSIP对话不知道会话的状态。它不知道INVITE会话是建立了还是断开了。事实上,PJSIP对话也不知道对话中是哪种会话。对话以一个会话激活,如果会话计数器为零,并且最后的事务也终止了,那么对话就会被销毁。
每个对话实例需要增加和减少会话计数器。
10.1.1 Dialog Sessions
PJSIP对话框架中,对话中的会话用引用计数器来表示。这个引用计数器被对话实例模型来增加或减少,当相应对话中创建或销毁一个会话时。
对话的会话被对话实例创建。一些对话中,一个对话实例可以创建不止一个会话(除了invite,一个对话中仅能创建一个invite会话)。
会话的具体表示在对话实例模型中定义。如上所述,基本的对话仅仅只关心当前活跃的会话数。
10.1.2 Dialog Usages
对话实例是注册到对话上用来接收对话事件的PJSIP模型。一个对话上可以注册多种模型,因此对话可以有多个实例。每个对话实例模型都可以处理一个指定的会话。例如,订阅的实例模型每次收到一个订阅的请求后都会创建一个新的订阅会话(并增加对话的会话计数器),当订阅终止的时候就会减少会话计数器。
对话对于对话实例的处理和endpoint处理模型类似;对于每个on_rx_request()和on_rx_response()事件,对话从高优先级模型开始传送至每个实例,直到其中有模型返回true,这时,对话就会停止事件的分发。on_tsx_state()通知将分发给所有对话实例。每个对话实例都需要过滤不属于它的实例。
在最基本(即底层)的使用中,应用程序直接管理对话,并且它是对话框的唯一“实例”(或用户)。这种情况下,应用负责管理对话中的会话,也就是处理所有的请求和响应并手动建立和关闭会话。
在后面的章节,我们会学习管理会话的高级API。这些高级API是作为对话实例注册到对话的PJSIP模型,他们会处理指定的每种会话的不同类型的SIP消息(一个invite实例模块将处理invite、PRACK、CANCEL、ACK、BYE、UPDATE和INFO,订阅实例模块将处理 REFER, SUBSCRIBE, and NOTIFY)。这个高级API根据指定的会话提供高级的回调。
这一章节我们也只学习基本的低级别的对话实例。
10.1.3 Dialog Set
每个对话都是一个对话集的成员。对话集有通用的本地tag定义(比如From tag)。通常一个对话集只有一个对话作为成员。一个对话集有多个对话的唯一一种情况就是INVITE fork了,在这种情况下每个收到不同To tag的响应都会在相同的对话集中创建一个新的对话。
对话集在PJSIP中的定义是不透明的类型(比如void*)。一个对话结构体(pjsip_dialog)有一个成员dlg_set来定义所属的对话集。应用程序可以使用链表API检索对话的兄弟姐妹(在同一个对话框集中)。
有关对话集的更多信息,请参阅10.1.6Forking。
10.1.4 Client Authentication
一个对话包含一个客户端认证会话( pjsip_auth_clt_sess),用于下游服务器的对话中的请求认证。基本对话用适当的认证头来初始化传出的请求,如果可用的话。但对话实例必须处理认证质询;比如基本对话不会收到401/407后自动的重发请求。
10.1.5 Class Diagram
下面的图表展示了UA层和基本对话框架。
图表展示了对话和实例之前的关系。
在最基本/底层的场景中,应用模块是对话的唯一实例。在更高级别下,一个高级的模块(例如pjsip_invite_usage和pjsip_subscribe_usage)可以注册到对话中作为对话实例,然后应用从他们的实例中获取事件而不是直接从对话中。
这个图标也展示了PJSIP的UA模型( pjsip_user_agent)。UA模型是所有对话的所有者,UA模型包含了一个当前活动的所有对话的哈希表。
10.1.6 Forking
Handling Forking Condition
UA模型提供了一个应用可注册的回调,当检测到下游代理服务器forked reponse(分叉响应)。一个分叉响应被定义为对话中的响应(可以是临时的或2xx的响应),有不同于现有对话的to tag。当收到这样的响应时,UA将会调用回调 on_dlg_forked(),以参数来传递收到的响应和原始对话(对话是由应用最初创建)。
note:应用程序完全有责任处理分叉情况。
在收到一个分叉的临时响应后,应用要:
#忽略临时响应(可能一直等收到最后的2xx响应)或者
#创建一个新的对话(通过调用pjsip_dlg_fork ())。在这种情形下,之后从这个特定调用分支(call leg)收到的响应将发送到新的对话。
收到2xx的分叉响应后,应用要:
#决定终止这个特定的分支。这种情况下,应用将会从响应中构造ACK请求,发送ACK,然后构造一个BYE事务,并发送到调用分支。应用在发送请求到事务或传输层之前,必须手动为ACK和BYE请求构造Route 头,根据响应中的Record-Route头。
#为这个特定的分支创建对话(通过调用pjsip_dlg_fork ())。应用之后构造和发送ACK请求到调用分支来建立对话。对话建立之后,应用可以终止对话通过发送BYE请求。
应用不能忽略一个分叉的2xx响应。
Creating Forked Dialog
应用通过调用pjsip_dlg_fork()函数来创建一个分叉对话。这个函数创建对话并有以下功能:
#复制原始对话(包括认证客户端会话)的所有属性到一个新的对话中。
#根据响应中的TO tag来分派不同的远端tag值
#注册新的对话到UA对话集
#如果原始对话有应用计时器,将会复制计时器并在新的对话中更新计时器。
注意这个函数不会从原始对话复制对话实例(比如模型)。
note:函数pjsip_dlg_fork()不从原始对话中复制对话实例的原因是,每次使用通常都有特定于对话的数据,在不了解数据语义的情况下无法复制这些数据。
新对话注册后,应用必须调用 pjsip_dlg_add_usage()来用新的对话来重新初测每一个对话实例。
新对话必须作为函数的返回值返回。这样UA会分发新消息到新对话,对话实例代表新对话来接收on_rx_response()通知。
Using Timer to Handle Failed Forked Dialog
应用可以调用pjsip_dlg_start_app_timer()函数来管理应用指定的计时器。对于与对话关联的计时器,这个计时器优于一般作用的计时器,因为对话销毁的时候计时器会自动删除。
计时器对处理失败的分叉对话很重要。一个分叉的early(早期or响铃?)会话可能不会以最终响应结束,因为分叉代理服务器收到2xx响应则不会转发300-699。所以终止这些挂起的会话就要通过在对话中设置timer。
使用对话的应用程序timer来处理失败的分叉早期对话的最好的方式是在收到对话集中的一个对话的2xx响应时就在另一个分叉对话中开始一个timer。当收到timer周期和2xx响应后,对话就要被终止。
10.1.7 CSeq Sequencing
对话的本地cseq在请求发送后会更新(与创建请求相反)。当请求中有CSeq,这个值在请求发出去后更新。
对话中远端的cseq在请求收到后更新。当对话中远端的cseq为空,第一个收到的请求将会设置对话的远端cseq。对于之后的请求,如果对话收到的cseq比之前记录下来的cseq低,那么这个请求将以500响应来无状态自动应答。当比记录的大时,对话将自动更新远端cseq。
note:此行为遵循SIP规范RFC 3261第12.2.2节。
10.1.8 Transactions
对话通常是有状态的活动。当有到来的请求时会自动创建UAS事务,当发出请求时创建UAC。
唯一的一种情形,对话会无状态的活动就是收到请求cseq比当前的小,这种会以500应答。
当对话(通过对话框API,用于UAS和UAC事务)创建事务时,事务的TU设置为UA引用,对话引用将放到事务的mod_data的适当索引中。所以是UA模型的module ID。当事件或消息到达时,事务向UA模型报告事件,然后模型接收并传送事件到对话。
10.2 Basic UA API Reference
10.2.1 User Agent Module API
typedef pjsip_module pjsip_user_agent;
UA类型
pj_status_t pjsip_ua_init_module(pjsip_endpoint *endpt,const pjsip_ua_init_param *prm);
pjsip_user_agent* pjsip_ua_instance(void);
获取UA实例
pj_status_t pjsip_ua_destroy(void);
销毁UA模型
10.2.2 Dialog Structure
<pjsip/sip_dialog.h>中声明了对话的结构体和API。下面的代码描述了 pjsip_dialog。
// This structure is used to describe dialog's participants, local and remote party.
struct pjsip_dlg_party
{
pjsip_fromto_hdr *info; // From/To header, inc tag
pj_uint32_t tag_hval; // Hashed value of the tag
pjsip_contact_hdr *contact; // Contact header.
pj_int32_t first_cseq; // First CSeq seen.
pj_int32_t cseq; // Next sequence number.
};
// This structure describes basic dialog.
struct pjsip_dialog
{
PJ_DECL_LIST_MEMBER(pjsip_dialog); // List node in dialog set.
// Static properties:
char obj_name[PJ_MAX_OBJ_NAME]; // Log identification
pj_pool_t *pool; // Dialog’s memory pool.
pj_mutex_t *mutex; // Dialog's mutex.
pjsip_user_agent *ua; // User agent instance.
void *dlg_set; // The dialog set.
// Dialog session properties.
pjsip_uri *target; // Current target.
pjsip_dlg_party local; // Local party info.
pjsip_dlg_party remote; // Remote party info.
pjsip_role_e role; // Initial role.
pj_bool_t secure; // Use secure transport?
pjsip_cid_hdr *call_id; // Call-ID header.
pjsip_route_hdr route_set; // Route set list.
pjsip_auth_clt_sess auth_sess; // Client authentication session.
// Session Management
int sess_count; // Session counter.
int tsx_count; // Active transaction counter.
// Dialog usages
unsigned usage_cnt; // Number of registered usages.
pjsip_module *usage[PJSIP_MAX_MODULE]; // Usages, priority sorted
// Module specific data.
void *mod_data[PJSIP_MAX_MODULE];
}
10.2.3 Dialog Creation API
通过调用下面的函数可以创建一个对话。
pj_status_t pjsip_dlg_create_uac( pjsip_user_agent *ua,
const pj_str_t *local_uri,
const pj_str_t *local_contact_uri,
const pj_str_t *remote_uri,
const pj_str_t *target,
pjsip_dialog **p_dlg);
创建一个新的对话,并在p_dlg参数中返回实例。创建对话后,应用程序可以通过调用 pjsip_dlg_add_usage()将模块作为对话实例添加。
注意初始化,会话计数器将初始化为0。
pj_status_t pjsip_dlg_create_uas( pjsip_user_agent *ua,
pjsip_rx_data *rdata,
const pj_str_t *contact,
pjsip_dialog **p_dlg);
从创建对话的传入请求中找到的信息来初始化UAS对话(比如INVITE,REFER, or SUBSCRIBE),并设置本地联系人为contact。如果contact没有指定,本地联系人用请求的To头中的URI来初始化。
如果请求由TO tag参数,对话的本地tag就用这个值来初始化。否则就调用一个全局唯一的id生成器来创建本地tag。
这个函数根据请求Record-Route头来初始化对话的路由集,如果存在的话。
注意初始化的时候,对话的会话计数器将初始化为零。
pj_status_t pjsip_dlg_fork( pjsip_dialog *original_dlg,
pjsip_rx_data *rdata,
pjsip_dialog **new_dlg );
在收到的分叉响应的rdata中创建一个新的(分叉的)对话。这个函数从original_dlg(包括身份验证会话)克隆一个新的对话,但是新的对话的remote tag是从响应的TO头中复制的。返回时,new_dlg将已注册到user_agent。应用只需要增加模型来作为对话实例。
注意初始化的时候对话的计数器为零。
10.2.4 Dialog Termination
一旦会话计数器为零,并且所有绑定的事务都终止了,对话通常会自动销毁。可是,也有一些情况下对话实例需要提前销毁对话,比如初始化失败的时候。
pjsip_dlg_terminate()函数用来提前销毁对话。这个函数通常被对话实例调用。应用也应该使用适当的高级别的会话API,例如 pjsip_inv_terminate(),将会销毁会话和对话。
pj_status_t pjsip_dlg_terminate( pjsip_dialog *original_dlg );
销毁对话并从UA模型的哈希表中注销。只有会话计数器为零才会被调用。
10.2.5 Dialog Session Management API
下面的函数用来管理对话的会话计数器。
pj_status_t pjsip_dlg_inc_session( pjsip_dialog *dlg );
增加对话中会话的数量。注意初始的时候(创建后)对话已经设置会话计数器为1。
pj_status_t pjsip_dlg_dec_session( pjsip_dialog *dlg );
减少会话数量。一旦会话计数器为零,且无事务绑定,对话就销毁。注意这个函数调用的时候如果没有事务绑定,将会立即销毁对话。
10.2.6 Dialog Usages API
下面的函数用来管理对话实例。
pj_status_t pjsip_dlg_add_usage( pjsip_dialog *dlg,
pjsip_module *module,
void *mod_data );
增加一个模型作为对话实例,并可选的设置模型特定的数据。
pj_status_t pjsip_dlg_set_mod_data( pjsip_dialog *dlg,
int module_id,
void *data );
附加模型指定数据到对话。
void* pjsip_dlg_get_mod_data( pjsip_dialog *dlg,
int module_id);
获取之前附加到对话中的模型指定数据。应用可以通过 dlg->mod_data[module_id]直接取值。
10.2.7 Dialog Request and Response API
pj_status_t pjsip_dlg_create_request( pjsip_dialog *dlg,
const pjsip_method *method,
int cseq,
pjsip_tx_data **tdata)
创建一个基本/通用的请求通过指定的method和可选的指定cseq。cseq使用01来自动的为请求放入下一个cseq。否则对于其他请求,比如CANCEL或者ACK,应用必须将CSeq作为参数放到原始的INVITE请求中。这个函数还可以在适当的地方放置Contact。
pj_status_t pjsip_dlg_send_request ( pjsip_dialog *dlg,
pjsip_tx_data *tdata,
pjsip_transaction **p_tsx );
向远端点发送请求消息。如果不是ACK的请求,对话有状态的发送请求消息,通过创建UAC事务以及用这个事务来发送请求。当请求不是ACK和CANCEL时,对话将增加本地的cseq并根据对话的cseq来更新请求中的cseq。
注意 对话实例的on_tsx_state回调调用可能在函数返回之前。
如果p_tsx不是NULL,这个参数将会和用于发送请求的事务实例一起设置。
无论操作的状态如何,此函数都会减少传输数据的引用计数器。
pj_status_t pjsip_dlg_create_response( pjsip_dialog *dlg,
pjsip_rx_data *rdata,
int st_code,
const pj_str_t *st_text,
pjsip_tx_data **tdata);
为rdata中状态为st_code且可选状态描述为st_text的请求创建响应消息。这个函数和endpoint的API pjsip_endpt_create_response() 不同的是对话的函数在响应中适当的增加Contact头和Record-Route头。
pj_status_t pjsip_dlg_modify_response( pjsip_dialog *dlg,
pjsip_tx_data *tdata,
int st_code,
const pj_str_t *st_text);
用其他的状态码来修改之前发送的响应。适当时增加Contact头。
pj_status_t pjsip_dlg_send_response( pjsip_dialog *dlg,
pjsip_transaction *tsx,
pjsip_tx_data *tdata);
有状态的发送响应消息。这个事务实例必须是在 on_rx_request()回调中报告过得。
无论操作的状态如何,此函数都会减少传输数据的引用计数器。
10.2.8 Dialog Auxiliary API
pj_status_t pjsip_dlg_set_route_set( pjsip_dialog *dlg,
const pjsip_route_hdr *route_set );
设置对话的初始路由集为route_set列表。在任何请求发出去前,这个函数只能被UAC对话调用。对话建立之后,路由集就不能改变了。
对于UAS对话,路由集在 pjsip_dlg_create_uas()中初始化,这个值来自收到请求中的Record-Route中。
route_set参数是Route头的标准列表(带sentinel)。
pj_status_t pjsip_dlg_start_app_timer( pjsip_dialog *dlg,
int app_id,
const pj_time_val *interval,
void (*cb)(pjsip_dialog*,int));
用对话来开启应用计时器,并指定应用的id app_id ,回调cb。应用只能为每个对话设置一个应用计时器。这个计时器比对话指定的计时器更加有效,因为它可以在对话销毁时自动销毁。注意这个计时器还可以在分叉对话中复制。
pj_status_t pjsip_dlg_stop_app_timer( pjsip_dialog *dlg );
如果开启,则停止应用指定的timer。
pjsip_dialog* pjsip_rdata_get_dlg( pjsip_rx_data *rdata );
在收到的rdata中获取对话实例。如果收到的消息和已存在的对话匹配,UA必须将匹配的对话实例放到rdata中,否则这个函数将消息在未匹配到任何存在的对话后返回NULL。
pjsip_dialog* pjsip_tsx_get_dlg( pjsip_transaction *tsx );
在指定的事务中获取对话实例。
10.3 Examples
10.3.1 Invite UAS Dialog
下面的例子使用了基本/低级别的对话API来处理传入的对话。这些例子展示了:
#建立和初始化传入对话
#创建UAS事务来处理传入的INVITE请求并发送1xx响应
#发送2xx的可靠响应
#处理收到的ACK
通常来说,许多的错误处理都被简单的省略了。真正的应用应该随时准备处理各种情形下的错误。
Creating Initial Invite Dialog
在这个例子中我们将学习怎样为传入的INVITE请求创建一个对话并且使用180/Ringing来进行临时响应。
pj_bool_t on_rx_request(pjsip_rx_data *rdata)
{
if (rdata->msg->line.request.method.id == PJSIP_INVITE_METHOD &&
pjsip_rdata_get_dlg(rdata) == NULL)
{
// Process incoming INVITE!
pjsip_dialog *dlg;
pjsip_transaction *tsx;
pjsip_tx_data *tdata;
struct app_dialog *app_dlg;
// Create, initialize, and register new dialog for incoming INVITE.
// This also implicitly create UAS transaction for rdata.
status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata, NULL, &dlg);
// Register application as the only dialog usage
status = pjsip_dlg_add_usage( dlg, &app_module, NULL );
// Increment session.
pjsip_dlg_inc_session(dlg);
// Create 180/Ringing response
status = pjsip_dlg_create_response( dlg, rdata, 180, NULL /*Ringing*/, &tdata);
// Send 180 response statefully. A transaction will be created in &tsx.
status = pjsip_dlg_send_response( dlg, pjsip_rdata_get_tsx(rdata), tdata);
// As in real application, normally we will send 200/OK later,
// when the user press the “Answer” button. In this example, we’ll send
// 200/OK in answer_dlg() function which will be explained later. In order
// to do so, we must “save” the INVITE transaction. We do this by putting
// the transaction instance in dialog’s module data at index application
// module’s ID.
//
dlg->mod_data[app_module.id] = pjsip_rdata_get_tsx(rdata);
// Done processing INVITE request
return PJ_TRUE;
}
// Process other requests
...
}
Answering Dialog
这个例子我们学习怎么发送200/OK来创建对话。
static void answer_dlg(pjsip_dlg *dlg)
{
pjsip_transaction *invite_tsx;
pjsip_tx_data *tdata;
invite_tsx = dlg->mod_data[app_module.id];
// Modify previously sent (provisional) response to 200/OK response.
// The previously sent message is found in tsx->last_tx.
tdata = invite_tsx->last_tx;
status = pjsip_dlg_modify_response( dlg, tdata, 200, NULL /*OK*/ );
// You may modify the response before it’s sent
// (e.g. add msg body etc).
...
// Send the 200 response using previous transaction.
// Transaction will take care of the retransmission.
status = pjsip_dlg_send_response( dlg, invite_tsx, tdata);
// We don’t need to keep pending invite tsx anymore.
dlg->mod_data[app_module.id] = NULL;
}
Processing CANCEL Request
这个例子学习处理收到的CALCEL请求。
pj_bool_t on_rx_request(pjsip_rx_data *rdata)
{
...
if (rdata->msg->line.request.method.id == PJSIP_CANCEL_METHOD)
{
// See if we have pending INVITE transaction.
pjsip_dialog *dlg;
pjsip_transaction *invite_tsx;
// All requests within a dialog will have the dialog instance
// recorded in rdata.
dlg = pjsip_rdata_get_dlg(rdata);
if (!dlg) {
// Not associated with any dialog. Respond statelessly with 481.
status = pjsip_endpt_respond_stateless( endpt, rdata, 481, NULL, NULL,
NULL, NULL);
return PJ_TRUE;
}
invite_tsx = dlg->mod_data[app_module.id];
if (invite_tsx) {
pjsip_tx_data *tdata;
// Transaction found. Respond CANCEL (statefully!) with 200 regardless
// whether the INVITE transaction has completed or not.
status = pjsip_dlg_respond( dlg, rdata, 200, NULL /*OK*/);
// Respond the INVITE transaction with 487/Request Terminated
// only when INVITE transaction has not send final response.
if (invite_tsx->status_code < 200) {
tdata = invite_tsx->last_tx;
status = pjsip_dlg_modify_response( dlg, tdata, 487, NULL );
// Send the 487 response.
status = pjsip_dlg_send_response( dlg, invite_tsx, tdata);
dlg->mod_data[app_module.id] = NULL;
// Decrement session!
pjsip_dlg_dec_session(dlg);
}
} else {
// Transaction not found, respond CANCEL with 481 (statefully!)
status = pjsip_dlg_respond ( dlg, rdata, 481, NULL );
}
// Done processing CANCEL request
return PJ_TRUE;
}
// Process other requests
...
}
Processing ACK Request
学习处理收到的ACK请求
pj_bool_t on_rx_request(pjsip_rx_data *rdata)
{
...
if (rdata->msg->line.request.method.id == PJSIP_ACK_METHOD &&
pjsip_rdata_get_dlg(rdata) != NULL)
{
// Process the ACK request
pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
...
return PJ_TRUE;
}
...
}
10.3.2 Outgoing Invite Dialog
下面的例子展示了怎么使用传出INVITE对话。
Creating Initial Dialog
static pj_status_t make_call( const pj_str_t *local_info, const pj_str_t *remote_info)
{
pjsip_dialog *dlg;
pjsip_tx_data *tdata;
// Create and initialize dialog.
status = pjsip_dlg_create_uac( user_agent, local_info, local_info,
remote_info, remote_info, &dlg );
// Register application as the only dialog usage.
status = pjsip_dlg_add_usage( dlg, &app_module, NULL);
// Add session.
pjsip_dlg_inc_session(dlg);
// Send initial INVITE.
status = pjsip_dlg_create_request( dlg, &pjsip_invite_method, -1, &tdata);
// Modify the INVITE (e.g. add message body etc.. )
...
// Send the INVITE request.
status = pjsip_dlg_send_request( dlg, tdata, NULL);
// Done.
// Further responses will be received in on_rx_response.
return status;
}
Receiving Response
static pj_bool_t on_rx_response( pjsip_rx_data *rdata )
{
pjsip_dialog *dlg;
dlg = pjsip_rdata_get_dlg( rdata );
if (dlg != NULL ) {
pjsip_transaction *tsx = pjsip_rdata_get_tsx( rdata );
if ( tsx != NULL && tsx->method.id == PJSIP_INVITE_METHOD) {
if (tsx->status_code < 200) {
PJ_LOG(3,(“app”, “Received provisional response %d”, tsx->status_code));
} else if (tsx->status_code >= 300) {
PJ_LOG(3,(“app”, “Dialog failed with status %d”, tsx->status_code));
pjsip_dlg_dec_session(dlg);
// ACK for non-2xx final response is sent by transaction.
} else {
PJ_LOG(3,(“app”, “Received OK response %d!”, tsx->status_code));
send_ack( dlg, rdata );
}
}
else if (tsx == NULL && rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD
&& rdata->msg_info.msg->line.status.code/100 == 2)
{
// Process 200/OK response retransmission.
send_ack( dlg, rdata );
}
return PJ_TRUE;
}
else
// Process other responses not belonging to any dialog
...
}
Sending ACK
static void send_ack( pjsip_dialog *dlg, pjsip_rx_data *rdata )
{
pjsip_tx_data *tdata;
// Create ACK request
status = pjsip_dlg_create_request( dlg, &pjsip_ack_method,
rdata->msg_info.cseq->cseq, &tdata );
// Add message body
...
// Send the request.
status = pjsip_dlg_send_request ( dlg, tdata, NULL );
}
10.3.3 Terminating Dialog
下面的例子展示了终止INVITE对话的一种方式,比如发送BYE
static void send_bye( pjsip_dialog *dlg )
{
pjsip_tx_data *tdata;
// Create BYE request
status = pjsip_dlg_create_request( dlg, &pjsip_bye_method, -1, &tdata );
// Send the request.
status = pjsip_dlg_send_request ( dlg, tdata, NULL );
// Decrement session.
// Dialog will be destroyed once the BYE transaction terminates.
pjsip_dlg_dec_session(dlg);
}