Chapter 7:Sending Messages
SIP应用的核心操作就是收发消息。 跟第一章描述的一样,模块调用on_rx_request()和on_rx_response()来处理传入的消息。
本章介绍发送传出消息的基本方式,即不使用transaction和dialog。
下一个章节介绍 transaction是如何处理有状态的请求(包括传入和传出的请求)。
7.1 Sending Messages Overview
7.1.1 Creating Messages
PJSIP提供了多种API来创建和响应消息。下面是创建消息的一些方式:
#对于响应消息,最开始使用 pjsip_endpt_create_response()函数
#对于请求消息,可以使用函数 pjsip_endpt_create_request(),pjsip_endpt_create_request_from_hdr(), pjsip_endpt_create_ack(), pjsip_endpt_create_cancel()
#代理调用pjsip_endpt_create_request_fwd()和pjsip_endpt_create_response_fwd () ,可以根据要转发的传入消息,创建请求和响应消息。
# 或者你可以调用pjsip_endpt_create_tdata()建立传输缓冲区来创建请求和响应消息,调用 pjsip_msg_create()创建消息,调用pjsip_msg_add_hdr()或pjsip_msg_insert_first_hdr ()增加消息的头字段,或者消息体。
#更高层的模块(dialog layer)可以提供更多的方式来创建消息。这个描述在各个模块的文档中。
所有创建API的消息(除了低级别的 pjsip_endpt_create_tdata())都设置transmit buffer(pjsip_tx_data)的计数器为1,也就是说有时候应用程序(或堆栈)必须减少计数器来销毁transmit buffer。
所有发送API的消息必须减少transmit buffer的计数器,也就是说只要应用程序不对transmit buffer的引用计数器做任何事情,buffder就必须在发送后销毁。
7.1.2 Sending Messages
发送消息的最基本方式是调用pjsip_endpt_acquire_transport()和pjsip_transport_send ()。然而,要使其工作,必须知道发送消息的目标地址(即sockaddr,而不仅仅是hostname)。有下面几个步骤来获取消息和已存在的socket地址(决定使用哪种地址,RFC3263),这个函数级别太低了因此不能直接使用。
发送消息的核心API是pjsip_endpt_send_request_stateless()和pjsip_endpt_send_response ()。从传输层的自动处理上来说是很有用的两个函数,也是上层模块(transaction)使用的最基本的组成部分。
pjsip_endpt_send_request_stateless()用来发送请求消息,有以下步骤:
#根据Request-URI和Route header里的参数判断要连接的地址
#用RFC3263(SIP定位服务器)的步骤来解析目的服务器
#选择和建立连接服务器所需的传输
#修改传入的via header来反映当前所用的传输
#如果服务器不能使用当前的传输来连接,转移错误到下一个服务器/传输
pjsip_endpt_send_response ()函数用来发送响应消息,有以下步骤:
#按照RFC3261的18.2.2小节的步骤来选择使用何种传输,向哪个地址发送响应
#另外rport参数符合RFC3581
#使用选择的传输发送响应
#如果使用选择的传输发送响应失败,转移失败到下一个地址,必要时根据RFC3263解析服务器
因为消息可以异步发出(比如TCP连接后),这两种函数都提供通知应用程序传输状态的回调。这个回调还通知应用将发生的失败,应用就有机会去避免这种行为。
7.2 Function Reference
7.2.1 Sending Response
Base Functions
pj_status_t pjsip_endpt_create_response( pjsip_endpoint *endpt,
pjsip_rx_data *rdata,
int st_code,
const char *st_text,
pjsip_tx_data **tdata);
使用状态码st_code和状态文本st_text为rdata中的请求创建标准响应消息。如果st_text是NULL,将使用默认的状态文本。
pj_status_t pjsip_get_response_addr( pj_pool_t *pool,
pjsip_rx_data *rdata,
pjsip_response_addr *res_addr);
根据 rdate中收到的请求来判断发送响应的地址(和传输)。该函数遵循RFC 3261和RFC 3581中18.2.2节中的规范,用于计算目标地址和传输。发送响应的地址和传输信息将在res_addr中返回。
pj_status_t pjsip_endpt_send_response( pjsip_endpoint *endpt,
pjsip_response_addr *res_addr,
pjsip_tx_data *response,
void *token,
void (*cb)( pjsip_send_state*,
pj_ssize_t sent,
pj_bool_t *cont));
使用res_addr中的目标地址和传输来无状态的发送response的响应。响应地址信息(res_addr)通常调用 pjsip_get_response_addr()来初始化。
调用回调的cb会报告定义的传输状态,还有存在pjsip_send_state中的其他信息(包括原始token)。如果消息成功发送,回调的sent参数为一个非零正值。如果失败了,sent为负值,错误码为正值(status = -sent)。退出回调的时候,可以通过将该参数置为零来避免应用程序尝试其他地址。
如果应用没有指定cb,当使用选择的传输分发消息失败了,也不再尝试下一个地址。消息是有效的,或者非零错误码返回PJ_SUCCESS。但是,即使返回了PJ_SUCCESS,也不能保证响应成功发出去了。
注意回调可能在函数返回之前调用。
Composite Functions
pj_status_t pjsip_endpt_respond_stateless( pjsip_endpoint *endpt,
pjsip_rx_data *rdata,
int st_code,
const char *st_text,
const pjsip_hdr *hdr_list,
const pjsip_msg_body *body);
这个函数为收到的请求创建和发送响应。除此之外,调用者可以指定消息体和附加的header放到响应消息的hdr_list和body参数里。如果没有附加的header和body发送,参数为NULL。
如果响应成功创建并发送到传输层或者返回了非零错误码,则返回PJ_SUCCESS,。但即使返回了PJ_SUCCESS,也不确保响应被成功发送。
7.2.2 Sending Request
pj_status_t pjsip_endpt_create_tdata( pjsip_endpoint *endpt,
pjsip_tx_data **tdata);
创建一个新的空的传输数据。
pj_status_t pjsip_endpt_create_request( pjsip_endpoint *endpt,
const pjsip_method *method
const pj_str_t *target,
const pj_str_t *from,
const pj_str_t *to,
const pj_str_t *contact,
const pj_str_t *call_id,
int cseq,
const pj_str_t *text,
pjsip_tx_data **p_tdata);
为指定的target URI,from,to和contact用指定的method创建新的请求消息。call_id和cseq是可选的。如果指定了text,要增加text/plain 消息体。请求消息的引用计数器初始化为1,在p_tdata中返给发送方。
pj_status_t pjsip_endpt_create_request_from_hdr(pjsip_endpoint *endpt,
const pjsip_method *method,
const pjsip_uri *target,
const pjsip_from_hdr *from,
const pjsip_to_hdr *to,
const pjsip_contact_hdr *ch,
const pjsip_cid_hdr *call_id,
int cseq,
const pj_str_t *text,
pjsip_tx_data **p_tdata);
从指定的参数中浅克隆header然后创建一个新的请求header。
pj_status_t pjsip_endpt_create_ack( pjsip_endpoint *endpt,
const pjsip_tx_data *tdata,
const pjsip_rx_data *rdata,
pjsip_tx_data **ack );
根据收到的响应rdata,从原始请求tdata中创建ACK请求消息。事务未成功收到INVITE的响应使用此函数。成功的INVITE响应的ACK请求由dialog的函数创建(create request)。
pj_status_t pjsip_endpt_create_cancel( pjsip_endpoint *endpt,
const pjsip_tx_data *tdata,
pjsip_tx_data **p_tdata);
根据之前发送的tdata中的请求创建CANCEL请求,在p_tdata中创建一个新的传输数据。
pj_status_t pjsip_endpt_send_request_stateless(pjsip_endpoint *endpt,
pjsip_tx_data *tdata,
void *token,
void (*cb)(pjsip_send_state*,
pj_ssize_t sent,
pj_bool_t *cont));
发送tdata无状态请求。这个函数关注的是根据消息中的信息判断目的地和所使用的传输以及request line中的URI和Route header。这个函数有以下几个步骤:
#根据 Request-URI和 Route headers (pjsip_get_request_addr())确定连接的host
#解析目的host (pjsip_endpt_resolve())
#获取所用的传输 (pjsip_endpt_acquire_transport())
#发送消息 (pjsip_transport_send())
#必要时将失败转入下一个地址或传输
调用回调cb将报告定义的传输状态,以及其他放到 pjsip_send_state中的信息(包括原始的token)。如果消息成功发送,回调的sent参数为一个非零正值。如果失败,sent是负值,错误码是这个值的正数(status= -sent)。如果cont参数的值是非零,就认为这函数可以尝试其他的地址发送消息(fail-over)。通过在退出回调的时候设置这个参数为零使应用不再尝试其他的地址。
如果应用没有指定回调的cb,那么所选传输分发消息失败也不会再尝试下一个的地址。
消息是有效的或返回了非零错误码则返回PJ_SUCCESS,不过即使是返回PJ_SUCCESS也不确保请求成功发出去了。
注意回调可能在函数返回之前被调用。
7.2.3 Stateless Proxy Forwarding(无状态代理转发)
代理可以选择转发一个无状态的请求。如果要这么做必须严格按照在RFC 3261的第16.11节Stateless Proxy中的规则。
pj_status_t pjsip_endpt_create_request_fwd( pjsip_endpoint *endpt,
pjsip_rx_data *rdata,
const pjsip_uri *uri,
const pj_str_t *branch,
unsigned options,
pjsip_tx_data **tdata);
创建新请求消息向上转发给uri。新请求深/全克隆收到的rdata中的请求,除非options中有其他的拷贝机制。branch参数如果不为NULL,用作Via header中的 branch-param。如果是NULL,将使用唯一的branch参数。
pj_status_t pjsip_endpt_create_response_fwd( pjsip_endpoint *endpt,
pjsip_rx_data *rdata,
unsigned options,
pjsip_tx_data **tdata);
根据rdata的响应消息,代理创建并转发一个下行的新响应消息来转发。注意函数按照响应原样克隆,也就是说,不检查响应的有效性,也不会移除Via header的最顶部。这个函数会深/全克隆除了非options指定了的其他拷贝机制之外的响应。
pj_str_t pjsip_calculate_branch_id( pjsip_rx_data *rdata );
根据收到的请求消息,创建全局唯一的branch参数。这个函数确保了之后如果重发消息依然具有同样的branch id。
这个函数还可以用在循环检查程序中。如果代理的相同请求返回相同URL,就给它们赋予相同的branch id。
注意返回的字符串从rdate的池中分配。
7.3 Examples
7.3.1 Sending Responses
发送未找到账户的无状态响应
static pj_bool_t on_rx_request(pjsip_rx_data *rdata )
{
pjsip_account *acc;
pj_status_t status;
// Find account referred to in the request.
acc = ...
// Respond statelessly if account can not be found.
if (!acc) {
status = pjsip_endpt_respond_stateless( endpt, rdata, 404, NULL /*Not Found*/,
NULL, NULL, NULL);
return PJ_TRUE;
}
// Process the account
...
return PJ_TRUE;
}
处理认证失败的无状态响应
另一种发送无状态响应的方式
static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
{
pjsip_account *acc;
// Lookup acc.
acc = ...;
// Check authorization and handle failure statelessly
if (!pjsip_auth_authorize( acc, rdata->msg )) {
pjsip_proxy_authenticate_hdr *auth_hdr;
status = pjsip_endpt_create_response( endpt, rdata,
407, NULL /* Proxy Auth Required */,
&tdata);
// Add Proxy-Authenticate header.
status = pjsip_auth_create_challenge( tdata->pool, ..., &auth_hdr);
pjsip_msg_add_hdr( &tdata->msg, auth_hdr );
// Send response statelessly
status = pjsip_endpt_send_response( endpt, tdata, NULL);
return PJ_TRUE;
}
// Authorization success. Proceed to next stage..
...
return PJ_TRUE;
}
无状态重定向
static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
{
pjsip_account *acc;
pj_status_t status;
// Find the account referred to in the request.
acc = ...
if (!acc) {
status = pjsip_endpt_respond_stateless( endpt, rdata, 404, NULL /*Not Found*/,
NULL, NULL, NULL );
return PJ_TRUE;
}
//
// Send 301/Redirect message, specifying the Contact details in the response
//
status = pjsip_endpt_respond_stateless( endpt, rdata,
301, NULL /*Moved Temporarily*/,
&acc->contact_list, NULL, NULL);
return PJ_TRUE;
}
7.3.2 Sending Requests
发送无状态请求
void my_send_request()
{
pj_status_t status;
pjsip_tx_data *tdata;
// Create the request.
// Actually the function takes pj_str_t* argument instead of char*.
status = pjsip_endpt_create_request( endpt, // endpoint
method, // method
“sip:[email protected]”, // target URI
“sip:[email protected]”, // From:
“sip:[email protected]”, // To:
“sip:[email protected]”, // Contact:
NULL, // Call-Id
0, // CSeq#
NULL, // body
&tdata ); // output
// You may modify the message before sending it.
...
// Send the request statelessly (for whatever reason...)
status = pjsip_endpt_send_request_stateless( endpt, tdata, NULL);
}
7.3.3 Stateless Forwarding
无状态的转发
static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
{
pjsip_account *acc;
pjsip_tx_data *tdata;
pj_str_t branch_id;
pj_status_t status;
// Find the account specified in the request.
acc = ...
// Generate unique branch ID for the request.
branch_id = pjsip_calculate_branch_id( rdata );
// Create new request to be forwarded to new destination.
status = pjsip_endpt_create_request_fwd( endpt, rdata, dest, &branch_id, 0,
&tdata );
// The new request is good to send, but you may modify it if necessary
// (e.g. adding/replacing/removing headers, etc.)
...
// Send the request downstream
status = pjsip_endpt_send_request_stateless( endpt, tdata, NULL );
return PJ_TRUE;
}
//
// Forward response upstream
//
static pj_bool_t on_rx_response(pjsip_rx_data *rdata)
{
pjsip_tx_data *tdata;
pj_status_t status;
// Check that topmost Via is ours, strip top-most Via, etc.
...
// Create new tdata for the response.
status = pjsip_endpt_create_response_fwd( endpt, rdata, 0, &tdata );
// Send the response upstream
status = pjsip_endpt_send_response( endpt, tdata, NULL);
return PJ_TRUE;
}