pjsip信令传输层包含抽象层和传输实现层
抽象层代码: sip_transport.c
实现层代码: sip_transport_loop.c
sip_transport_tcp.c
sip_transport_tls.c
sip_transport_udp.c
本次重点研究sip传输抽象层和UDP传输实现层
在前面的分析中, 我们应该对pisip库的代码风格由一定了解了, 那就是: 统一的实例工厂接口函数族 + 统一的实例操作接口函数族。
1) 抽象层代码实现:
工厂接口注册
工厂操作接口函数族统一封装
实例操作接口函数族统一封装
2) 实现层代码实现
统一(函数原型)的工厂接口实现,例如实例的创建、销毁
统一(函数原型)的实例操作接口函数实现,例如,数据收发,参数读写
3)实例模块结构体的首个成员一定是一个抽象层对象
这样,才方便通过对指针的强制类型转换操作,实现由抽象层对象实例指针到实现层对象指针的转换(常用在实现层提供的回调函数中)
题外话, 如果实例模块结构体的首个成员bu是一个抽象层对象, 能够实现由抽象层对象实例指针到实现层对象指针的转换吗?
答案是可以滴!貌似linux内核源码中, 有个宏可以实现。
先面重点研究UDP层和sip传输抽象层两个模块, 看了看UDP层的结构体定义, 比较简单, 那先从简单的UDP模块开始吧!
================================================================================================
一) SIP UDP传输层
结构体定义如下:
struct udp_transport
{
pjsip_transport base;
pj_sock_t sock;
pj_ioqueue_key_t *key;
int rdata_cnt; //消息缓冲区待处理数量
pjsip_rx_data **rdata; //消息缓冲区(指针数组)
int is_closing; //接口正在关闭中, ioqueue获得的数据应该被抛弃
pj_bool_t is_paused; //接口暂停使用, ioqueue获得的数据应该被抛弃
int read_loop_spin; //udp_on_read_complete操作进入+1,离开-1
/* Group lock to be used by UDP transport and ioqueue key */
pj_grp_lock_t *grp_lock;
};
其中pjsip_transport抽象接口中封装的udp_transport实例操作接口如下, 只有3个接口函数:
struct pjsip_transport
{
。。。。
pjsip_tpfactory *factory; /**< Factory instance. Note: it may be invalid/shutdown. */
。。。
pj_status_t (*send_msg)(pjsip_transport *transport, pjsip_tx_data *tdata,
const pj_sockaddr_t *rem_addr,int addr_len, void *token, pjsip_transport_callback callback);
pj_status_t (*do_shutdown)(pjsip_transport *transport);
pj_status_t (*destroy)(pjsip_transport *transport);
};
再看看实例工厂接口函数, 也只有3个接口函数:
struct pjsip_tpfactory
{
。。。
pj_status_t (*create_transport)(pjsip_tpfactory *factory, pjsip_tpmgr *mgr, pjsip_endpoint *endpt,
const pj_sockaddr *rem_addr, int addr_len, pjsip_transport **transport);
。。。
pj_status_t (*create_transport2)(pjsip_tpfactory *factory, pjsip_tpmgr *mgr, pjsip_endpoint *endpt,
const pj_sockaddr *rem_addr, int addr_len, pjsip_tx_data *tdata, pjsip_transport **transport);
。。。
pj_status_t (*destroy)(pjsip_tpfactory *factory);
};
按正常的流程,当需要启动一个udp_transport进行SIP消息的收发时, 应该调用工厂函数create_transport/create_transport2创建udp_transport实例, 程序退出时使用destroy销毁。
但是代码中没有找到这个用法啊?参考pjsip提供的例子, 最后在pjsua_core.c中找到pjsua_transport_create()函数的定义, SIP传输层居然是这样建立起来的:
1) status = create_sip_udp_sock(pjsip_transport_type_get_af(type), cfg, &sock, &pub_addr);
2) pjsip_udp_transport_attach2(pjsua_var.endpt, type, sock,&addr_name, 1, &tp);
再看看例子simpleua.c中, 又是另外一种方式了, 根本没有用到工厂接口:
3) status = pjsip_udp_transport_start( g_endpt, &addr.ipv4, NULL, 1, NULL);
仔细看看工厂成员的定义的备注, 已经说明了工厂实例可能是invalid的:
pjsip_tpfactory *factory; /**< Factory instance. Note: it may be invalid/shutdown. */
好吧! 要想了解如果创建传输层实例, 多看例子程序就是了, 我们还是把重点放在SIP数据的收发吧!
与消息收发有关的实例操作接口只提供了消息发送接口: send_msg, 接口函数(udp_send_msg)的实现中, 使用libpj提供的ioqueue机制进行数据发送
sip传输层抽象层实现代码中, 使用pjsip_transport_send()对send_msg就行了二次封装, 消息发送的调用栈就是:
pjsip_transport_send()->udp_send_msg()->pj_ioqueue_sendto()->udp_on_write_complete()
udp_transport为ioqueue提供了两个回调函数, 用来通知“用户”, SIP消息的到来和SIP消息的发送结束
ioqueue_cb.on_read_complete = &udp_on_read_complete;
ioqueue_cb.on_write_complete = &udp_on_write_complete;
这两个回调函数的定义:
static void udp_on_read_complete( pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read);
static void udp_on_write_complete( pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bbytes_sent);
参数pj_ioqueue_key_t *key 相当于套接字的唯一key标记
参数pj_ioqueue_op_key_t *op_key又是什么呢? 按照一般的推测, 这个参数应该与 udp_transport的使用(调用)者有关系
===========================================
对于udp_on_write_complete()该参数的来源可以在udp_send_msg()中找到,代码片段如下:
tdata->op_key.tdata = tdata;
tdata->op_key.token = token;
tdata->op_key.callback = callback; //op_key成员包含了了SIP应用层的提供的回调函数及其参数信息
size = tdata->buf.cur - tdata->buf.start;
status = pj_ioqueue_sendto(tp->key, (pj_ioqueue_op_key_t*)&tdata->op_key,tdata->buf.start, &size, 0,rem_addr, addr_len);
第二个参数进行了强制转换操作: pjsip_tx_data_op_key* ====》 pj_ioqueue_op_key_t*
udp_on_write_complete()函数中,又转换回来: pj_ioqueue_op_key_t* ====》 pjsip_tx_data_op_key*
为什么可以有这种转换操作?
pjsip_tx_data_op_key第一个成员就是: pj_ioqueue_op_key_t key;
pj_ioqueue_op_key_t应该是ioqueue层操作所需的参数, 在传输层,使用pjsip_tx_data_op_key对pj_ioqueue_op_key_t做了一层封装
问:tdata->op_key.key 是在哪里初始化的呢?
答:在tdata的pjsip_tx_data_create()创建函数(udp_send_msg会调用它)中, pj_ioqueue_op_key_init(&tdata->op_key.key, sizeof(tdata->op_key.key));
udp_on_write_completete 解析出pjsip_tx_data_op_key中的抽象层回调参数, 使用回调参数中的抽象层回调函数继续向抽象层回调
发送完成的消息告知抽象层后,抽象层再使用pjsip_transport_send()参数中提供的令牌参数中携带的回调接口继续向应用层回调
抽象层提供了统一的上层回调入口函数:void transport_send_callback(pjsip_transport *transport, void *token, pj_ssize_t size)
{
pjsip_tx_data *tdata = (pjsip_tx_data*) token;
PJ_UNUSED_ARG(transport);
/* Mark pending off so that app can resend/reuse txdata from inside the callback.*/
tdata->is_pending = 0;
//这里使用pjsip_transport_send()参数表中提供的回调及令牌继续通知到更高层
if (tdata->cb) {
(*tdata->cb)(tdata->token, tdata, size);
}
/* Decrement reference count. */
pjsip_tx_data_dec_ref(tdata);
}
******** 经过运行例子程序对注册消息的跟踪, 发现:
应用层调用:pjsip_transport_send()->udp_send_msg()->pj_ioqueue_sendto()发丝能够注册消息时
UDP数据是同步发送的, pj_ioqueue_op_key_t *op_key参数及userdata参数未被使用, udp_on_write_complete()根本也未被回调
===== 因为并没有使用到ioqueue的poll机制 ======
===============================================
对于udp_on_read_complete(), pj_ioqueue_op_key_t *op_key又源于何处呢?
通过追查pj_ioqueue_poll()函数,进入ioqueue_dispatch_read_event()函数, 发现如下代码:
if (h->cb.on_read_complete && !IS_CLOSING(h))
(*h->cb.on_read_complete)(h, (pj_ioqueue_op_key_t*)read_op, bytes_read);
这个read_op从哪里来的呢?代码:read_op = h->read_list.next;
h->read_list貌似是待读(pending read)套接子空闲缓冲区的链表头部
每当poll事件发生, 就会pj_list_erase(read_op)删除空闲表,然后读套接子数据, 放在read_op->buf数据缓冲区内
在udp_on_read_complete()回调中, 调用pjsip_tpmgr_receive_packet()处理接收到的SIP数据包
然后循环调用pj_ioqueue_recvfrom从同一套接子接收新的SIP消息, 接收失败,调用pj_ioqueue_recvfrom(), 把read_op放入资源链表中
这样,资源链表永远都有备用的read_op放入资源, 消息循环就可以永不停止了
这个h->read_list缓冲区资源链表由是如何初始化的呢?
调用栈是这样的: start_async_read()->pj_ioqueue_recvfrom()->pj_list_insert_before()
当启动时, 会调用start_async_read()函数, 这个函数的作用就是通过调用pj_ioqueue_recvfrom(), 把read_op放入资源链表中
过程如下(以transport_attach函数为例):
1) tp = PJ_POOL_ZALLOC_T(pool, struct udp_transport);
2) init_rdata:
rdata->tp_info.pool = pool;
rdata->tp_info.transport = &tp->base;
rdata->tp_info.tp_data = (void*)(pj_ssize_t)rdata_index;
rdata->tp_info.op_key.rdata = rdata;
pj_ioqueue_op_key_init(&rdata->tp_info.op_key.op_key, sizeof(pj_ioqueue_op_key_t));
在函数pj_ioqueue_recvfrom()中, 出现了一个难以理解的指针装换操作:
PJ_DEF(pj_status_t) pj_ioqueue_recvfrom( pj_ioqueue_key_t *key,
pj_ioqueue_op_key_t *op_key, void *buffer, pj_ssize_t *length, unsigned flags, pj_sockaddr_t *addr, int *addrlen)
{
struct read_operation *read_op;
.........
read_op = (struct read_operation*)op_key; //这个转换的合理性在哪??? 注意pj_ioqueue_op_key_t的成员变量
.....
read_op->op = PJ_IOQUEUE_OP_RECV_FROM;
read_op->buf = buffer;
read_op->size = *length;
read_op->flags = flags;
read_op->rmt_addr = addr;
read_op->rmt_addrlen = addrlen;
}
请看: start_async_read中对pj_ioqueue_recvfrom的调用:
status = pj_ioqueue_recvfrom(tp->key,
&tp->rdata[i]->tp_info.op_key.op_key,
tp->rdata[i]->pkt_info.packet,
&size, PJ_IOQUEUE_ALWAYS_ASYNC,
&tp->rdata[i]->pkt_info.src_addr,
&tp->rdata[i]->pkt_info.src_addr_len);
注意第二个参数:tp->rdata[i]->tp_info.op_key.op_key, tpinfo的定义如下:
struct {
pj_pool_t *pool;/** Memory pool for this buffer. */
pjsip_transport *transport;/** The transport object which received this packet. */
void *tp_data;/** Other transport specific data to be attached to this buffer. */
pjsip_rx_data_op_key op_key;/** Ioqueue key. */
} tp_info;
typedef struct pjsip_rx_data_op_key
{
pj_ioqueue_op_key_t op_key; /**< ioqueue op_key. */
pjsip_rx_data *rdata; /**< rdata associated with this */
} pjsip_rx_data_op_key;
typedef struct pj_ioqueue_op_key_t
{
void *internal__[32]; /**< Internal I/O Queue data. */
void *activesock_data; /**< Active socket data. */
void *user_data; /**< Application data. */
} pj_ioqueue_op_key_t;
如果说, 上面的转换是下面的转换, 那应该容易理解:
pjsip_rx_data_op_key * rx_op_key = (pjsip_rx_data_op_key *)op_key;
但是实在难以理解: struct read_operation* read_op = (struct read_operation*)op_key;
struct read_operation
{
PJ_DECL_LIST_MEMBER(struct read_operation);
pj_ioqueue_operation_e op; //op是一个枚举类型的变量
void *buf;
pj_size_t size;
unsigned flags;
pj_sockaddr_t *rmt_addr;
int *rmt_addrlen;
};
//后来才明白, pj_ioqueue_op_key_t中定义了一个32个元素的指针数组, 该指针数组所占空间足以容纳下结构体struct read_operation:
//这个struct read_operation实际上是使用了pj_ioqueue_op_key_t的internal__数组空间的内存位置, 只要该用户清楚内存结构, 用户可以安全地使用它;
在pj_ioqueue_recvfrom中, 该片内存空间被强制转换成一个结构体struct read_operation, 并被保存在pj_ioqueue_key_t 的readlist链表当中
当ioqueue_poll轮询到有IO事件发生时, ioqueue_dispatch_read_event从readlist链表当中取出对应的指针,获得结构体struct read_operation的访问地址
在其他一些库的设计中, 也存在类似这种 void *internal__[32]预分配一定内存供库的的内部数据根据实际情况灵活使用的用法。
二) SIP 传输层抽象层
SIP传输层数据收发可能在不同的线程中操作,SIP 传输层抽象层对传输层实例使用引用计数机型安全释放
我们最关心的收发数据的封装函数
1) TX数据发送(If the message has been sent successfully, this function will return PJ_SUCCESS and the callback will not be called):
// a low-level function to send a SIP message
pjsip_transport_send(pjsip_transport *tr,pjsip_tx_data *tdata,const pj_sockaddr_t *addr,int addr_len,void *token, pjsip_tp_send_callback cb);
mgr->on_tx_msg()回调函数将在pjsip_transport_send中数据被真正发送到网络之前被调用
//a low-level function to send raw data to a destination
pjsip_tpmgr_send_raw(pjsip_tpmgr *mgr, pjsip_transport_type_e tp_type, const pjsip_tpselector *sel,
pjsip_tx_data *tdata, const void *raw_data, pj_size_t data_len, const pj_sockaddr_t *addr, int addr_len,
void *token, pjsip_tp_send_callback cb);
2) RX数据处理: pjsip_tpmgr_receive_packet()
重点在函数的最后: mgr->on_rx_msg(mgr->endpt, PJ_SUCCESS, rdata); 明显这是一个回调
sip_endpoint.c中提供了着个回调函数: endpt_on_rx_msg
抽象层代码: sip_transport.c
实现层代码: sip_transport_loop.c
sip_transport_tcp.c
sip_transport_tls.c
sip_transport_udp.c
本次重点研究sip传输抽象层和UDP传输实现层
在前面的分析中, 我们应该对pisip库的代码风格由一定了解了, 那就是: 统一的实例工厂接口函数族 + 统一的实例操作接口函数族。
1) 抽象层代码实现:
工厂接口注册
工厂操作接口函数族统一封装
实例操作接口函数族统一封装
2) 实现层代码实现
统一(函数原型)的工厂接口实现,例如实例的创建、销毁
统一(函数原型)的实例操作接口函数实现,例如,数据收发,参数读写
3)实例模块结构体的首个成员一定是一个抽象层对象
这样,才方便通过对指针的强制类型转换操作,实现由抽象层对象实例指针到实现层对象指针的转换(常用在实现层提供的回调函数中)
题外话, 如果实例模块结构体的首个成员bu是一个抽象层对象, 能够实现由抽象层对象实例指针到实现层对象指针的转换吗?
答案是可以滴!貌似linux内核源码中, 有个宏可以实现。
先面重点研究UDP层和sip传输抽象层两个模块, 看了看UDP层的结构体定义, 比较简单, 那先从简单的UDP模块开始吧!
================================================================================================
一) SIP UDP传输层
结构体定义如下:
struct udp_transport
{
pjsip_transport base;
pj_sock_t sock;
pj_ioqueue_key_t *key;
int rdata_cnt; //消息缓冲区待处理数量
pjsip_rx_data **rdata; //消息缓冲区(指针数组)
int is_closing; //接口正在关闭中, ioqueue获得的数据应该被抛弃
pj_bool_t is_paused; //接口暂停使用, ioqueue获得的数据应该被抛弃
int read_loop_spin; //udp_on_read_complete操作进入+1,离开-1
/* Group lock to be used by UDP transport and ioqueue key */
pj_grp_lock_t *grp_lock;
};
其中pjsip_transport抽象接口中封装的udp_transport实例操作接口如下, 只有3个接口函数:
struct pjsip_transport
{
。。。。
pjsip_tpfactory *factory; /**< Factory instance. Note: it may be invalid/shutdown. */
。。。
pj_status_t (*send_msg)(pjsip_transport *transport, pjsip_tx_data *tdata,
const pj_sockaddr_t *rem_addr,int addr_len, void *token, pjsip_transport_callback callback);
pj_status_t (*do_shutdown)(pjsip_transport *transport);
pj_status_t (*destroy)(pjsip_transport *transport);
};
再看看实例工厂接口函数, 也只有3个接口函数:
struct pjsip_tpfactory
{
。。。
pj_status_t (*create_transport)(pjsip_tpfactory *factory, pjsip_tpmgr *mgr, pjsip_endpoint *endpt,
const pj_sockaddr *rem_addr, int addr_len, pjsip_transport **transport);
。。。
pj_status_t (*create_transport2)(pjsip_tpfactory *factory, pjsip_tpmgr *mgr, pjsip_endpoint *endpt,
const pj_sockaddr *rem_addr, int addr_len, pjsip_tx_data *tdata, pjsip_transport **transport);
。。。
pj_status_t (*destroy)(pjsip_tpfactory *factory);
};
按正常的流程,当需要启动一个udp_transport进行SIP消息的收发时, 应该调用工厂函数create_transport/create_transport2创建udp_transport实例, 程序退出时使用destroy销毁。
但是代码中没有找到这个用法啊?参考pjsip提供的例子, 最后在pjsua_core.c中找到pjsua_transport_create()函数的定义, SIP传输层居然是这样建立起来的:
1) status = create_sip_udp_sock(pjsip_transport_type_get_af(type), cfg, &sock, &pub_addr);
2) pjsip_udp_transport_attach2(pjsua_var.endpt, type, sock,&addr_name, 1, &tp);
再看看例子simpleua.c中, 又是另外一种方式了, 根本没有用到工厂接口:
3) status = pjsip_udp_transport_start( g_endpt, &addr.ipv4, NULL, 1, NULL);
仔细看看工厂成员的定义的备注, 已经说明了工厂实例可能是invalid的:
pjsip_tpfactory *factory; /**< Factory instance. Note: it may be invalid/shutdown. */
好吧! 要想了解如果创建传输层实例, 多看例子程序就是了, 我们还是把重点放在SIP数据的收发吧!
与消息收发有关的实例操作接口只提供了消息发送接口: send_msg, 接口函数(udp_send_msg)的实现中, 使用libpj提供的ioqueue机制进行数据发送
sip传输层抽象层实现代码中, 使用pjsip_transport_send()对send_msg就行了二次封装, 消息发送的调用栈就是:
pjsip_transport_send()->udp_send_msg()->pj_ioqueue_sendto()->udp_on_write_complete()
udp_transport为ioqueue提供了两个回调函数, 用来通知“用户”, SIP消息的到来和SIP消息的发送结束
ioqueue_cb.on_read_complete = &udp_on_read_complete;
ioqueue_cb.on_write_complete = &udp_on_write_complete;
这两个回调函数的定义:
static void udp_on_read_complete( pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read);
static void udp_on_write_complete( pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bbytes_sent);
参数pj_ioqueue_key_t *key 相当于套接字的唯一key标记
参数pj_ioqueue_op_key_t *op_key又是什么呢? 按照一般的推测, 这个参数应该与 udp_transport的使用(调用)者有关系
===========================================
对于udp_on_write_complete()该参数的来源可以在udp_send_msg()中找到,代码片段如下:
tdata->op_key.tdata = tdata;
tdata->op_key.token = token;
tdata->op_key.callback = callback; //op_key成员包含了了SIP应用层的提供的回调函数及其参数信息
size = tdata->buf.cur - tdata->buf.start;
status = pj_ioqueue_sendto(tp->key, (pj_ioqueue_op_key_t*)&tdata->op_key,tdata->buf.start, &size, 0,rem_addr, addr_len);
第二个参数进行了强制转换操作: pjsip_tx_data_op_key* ====》 pj_ioqueue_op_key_t*
udp_on_write_complete()函数中,又转换回来: pj_ioqueue_op_key_t* ====》 pjsip_tx_data_op_key*
为什么可以有这种转换操作?
pjsip_tx_data_op_key第一个成员就是: pj_ioqueue_op_key_t key;
pj_ioqueue_op_key_t应该是ioqueue层操作所需的参数, 在传输层,使用pjsip_tx_data_op_key对pj_ioqueue_op_key_t做了一层封装
问:tdata->op_key.key 是在哪里初始化的呢?
答:在tdata的pjsip_tx_data_create()创建函数(udp_send_msg会调用它)中, pj_ioqueue_op_key_init(&tdata->op_key.key, sizeof(tdata->op_key.key));
udp_on_write_completete 解析出pjsip_tx_data_op_key中的抽象层回调参数, 使用回调参数中的抽象层回调函数继续向抽象层回调
发送完成的消息告知抽象层后,抽象层再使用pjsip_transport_send()参数中提供的令牌参数中携带的回调接口继续向应用层回调
抽象层提供了统一的上层回调入口函数:void transport_send_callback(pjsip_transport *transport, void *token, pj_ssize_t size)
{
pjsip_tx_data *tdata = (pjsip_tx_data*) token;
PJ_UNUSED_ARG(transport);
/* Mark pending off so that app can resend/reuse txdata from inside the callback.*/
tdata->is_pending = 0;
//这里使用pjsip_transport_send()参数表中提供的回调及令牌继续通知到更高层
if (tdata->cb) {
(*tdata->cb)(tdata->token, tdata, size);
}
/* Decrement reference count. */
pjsip_tx_data_dec_ref(tdata);
}
******** 经过运行例子程序对注册消息的跟踪, 发现:
应用层调用:pjsip_transport_send()->udp_send_msg()->pj_ioqueue_sendto()发丝能够注册消息时
UDP数据是同步发送的, pj_ioqueue_op_key_t *op_key参数及userdata参数未被使用, udp_on_write_complete()根本也未被回调
===== 因为并没有使用到ioqueue的poll机制 ======
===============================================
对于udp_on_read_complete(), pj_ioqueue_op_key_t *op_key又源于何处呢?
通过追查pj_ioqueue_poll()函数,进入ioqueue_dispatch_read_event()函数, 发现如下代码:
if (h->cb.on_read_complete && !IS_CLOSING(h))
(*h->cb.on_read_complete)(h, (pj_ioqueue_op_key_t*)read_op, bytes_read);
这个read_op从哪里来的呢?代码:read_op = h->read_list.next;
h->read_list貌似是待读(pending read)套接子空闲缓冲区的链表头部
每当poll事件发生, 就会pj_list_erase(read_op)删除空闲表,然后读套接子数据, 放在read_op->buf数据缓冲区内
在udp_on_read_complete()回调中, 调用pjsip_tpmgr_receive_packet()处理接收到的SIP数据包
然后循环调用pj_ioqueue_recvfrom从同一套接子接收新的SIP消息, 接收失败,调用pj_ioqueue_recvfrom(), 把read_op放入资源链表中
这样,资源链表永远都有备用的read_op放入资源, 消息循环就可以永不停止了
这个h->read_list缓冲区资源链表由是如何初始化的呢?
调用栈是这样的: start_async_read()->pj_ioqueue_recvfrom()->pj_list_insert_before()
当启动时, 会调用start_async_read()函数, 这个函数的作用就是通过调用pj_ioqueue_recvfrom(), 把read_op放入资源链表中
过程如下(以transport_attach函数为例):
1) tp = PJ_POOL_ZALLOC_T(pool, struct udp_transport);
2) init_rdata:
rdata->tp_info.pool = pool;
rdata->tp_info.transport = &tp->base;
rdata->tp_info.tp_data = (void*)(pj_ssize_t)rdata_index;
rdata->tp_info.op_key.rdata = rdata;
pj_ioqueue_op_key_init(&rdata->tp_info.op_key.op_key, sizeof(pj_ioqueue_op_key_t));
在函数pj_ioqueue_recvfrom()中, 出现了一个难以理解的指针装换操作:
PJ_DEF(pj_status_t) pj_ioqueue_recvfrom( pj_ioqueue_key_t *key,
pj_ioqueue_op_key_t *op_key, void *buffer, pj_ssize_t *length, unsigned flags, pj_sockaddr_t *addr, int *addrlen)
{
struct read_operation *read_op;
.........
read_op = (struct read_operation*)op_key; //这个转换的合理性在哪??? 注意pj_ioqueue_op_key_t的成员变量
.....
read_op->op = PJ_IOQUEUE_OP_RECV_FROM;
read_op->buf = buffer;
read_op->size = *length;
read_op->flags = flags;
read_op->rmt_addr = addr;
read_op->rmt_addrlen = addrlen;
}
请看: start_async_read中对pj_ioqueue_recvfrom的调用:
status = pj_ioqueue_recvfrom(tp->key,
&tp->rdata[i]->tp_info.op_key.op_key,
tp->rdata[i]->pkt_info.packet,
&size, PJ_IOQUEUE_ALWAYS_ASYNC,
&tp->rdata[i]->pkt_info.src_addr,
&tp->rdata[i]->pkt_info.src_addr_len);
注意第二个参数:tp->rdata[i]->tp_info.op_key.op_key, tpinfo的定义如下:
struct {
pj_pool_t *pool;/** Memory pool for this buffer. */
pjsip_transport *transport;/** The transport object which received this packet. */
void *tp_data;/** Other transport specific data to be attached to this buffer. */
pjsip_rx_data_op_key op_key;/** Ioqueue key. */
} tp_info;
typedef struct pjsip_rx_data_op_key
{
pj_ioqueue_op_key_t op_key; /**< ioqueue op_key. */
pjsip_rx_data *rdata; /**< rdata associated with this */
} pjsip_rx_data_op_key;
typedef struct pj_ioqueue_op_key_t
{
void *internal__[32]; /**< Internal I/O Queue data. */
void *activesock_data; /**< Active socket data. */
void *user_data; /**< Application data. */
} pj_ioqueue_op_key_t;
如果说, 上面的转换是下面的转换, 那应该容易理解:
pjsip_rx_data_op_key * rx_op_key = (pjsip_rx_data_op_key *)op_key;
但是实在难以理解: struct read_operation* read_op = (struct read_operation*)op_key;
struct read_operation
{
PJ_DECL_LIST_MEMBER(struct read_operation);
pj_ioqueue_operation_e op; //op是一个枚举类型的变量
void *buf;
pj_size_t size;
unsigned flags;
pj_sockaddr_t *rmt_addr;
int *rmt_addrlen;
};
//后来才明白, pj_ioqueue_op_key_t中定义了一个32个元素的指针数组, 该指针数组所占空间足以容纳下结构体struct read_operation:
//这个struct read_operation实际上是使用了pj_ioqueue_op_key_t的internal__数组空间的内存位置, 只要该用户清楚内存结构, 用户可以安全地使用它;
在pj_ioqueue_recvfrom中, 该片内存空间被强制转换成一个结构体struct read_operation, 并被保存在pj_ioqueue_key_t 的readlist链表当中
当ioqueue_poll轮询到有IO事件发生时, ioqueue_dispatch_read_event从readlist链表当中取出对应的指针,获得结构体struct read_operation的访问地址
在其他一些库的设计中, 也存在类似这种 void *internal__[32]预分配一定内存供库的的内部数据根据实际情况灵活使用的用法。
二) SIP 传输层抽象层
SIP传输层数据收发可能在不同的线程中操作,SIP 传输层抽象层对传输层实例使用引用计数机型安全释放
我们最关心的收发数据的封装函数
1) TX数据发送(If the message has been sent successfully, this function will return PJ_SUCCESS and the callback will not be called):
// a low-level function to send a SIP message
pjsip_transport_send(pjsip_transport *tr,pjsip_tx_data *tdata,const pj_sockaddr_t *addr,int addr_len,void *token, pjsip_tp_send_callback cb);
mgr->on_tx_msg()回调函数将在pjsip_transport_send中数据被真正发送到网络之前被调用
//a low-level function to send raw data to a destination
pjsip_tpmgr_send_raw(pjsip_tpmgr *mgr, pjsip_transport_type_e tp_type, const pjsip_tpselector *sel,
pjsip_tx_data *tdata, const void *raw_data, pj_size_t data_len, const pj_sockaddr_t *addr, int addr_len,
void *token, pjsip_tp_send_callback cb);
2) RX数据处理: pjsip_tpmgr_receive_packet()
重点在函数的最后: mgr->on_rx_msg(mgr->endpt, PJ_SUCCESS, rdata); 明显这是一个回调
sip_endpoint.c中提供了着个回调函数: endpt_on_rx_msg