pjsip学习笔记9

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


猜你喜欢

转载自blog.csdn.net/twd_1991/article/details/80654525