pjsip源码解析之pjsip的信令的发送与接收之接收UDP数据

本文主要对 sip 中的 sip_transport 在发送与接收过程中的流程,其余内容暂时不关注。本章主要关注 UDP 如何获取到数据并分发到上层中的。

首先我们要知道怎么启动接收 udp 数据,直接到初始化 udp 这个 transport 的地方看看。

欢迎进入q群761341723,大家一起讨论问题。hpng该网站为我自己网站,一些想法也会发到这里

transport_attach

transport_attach 函数的定义位于 pjsip/src/pjsip/sip_transport_udp.c 中,其中内容如下所示:

/*
 * pjsip_udp_transport_attach()
 *
 * Attach UDP socket and start transport.
 */
static pj_status_t transport_attach( pjsip_endpoint *endpt,
                                     pjsip_transport_type_e type,
                                     pj_sock_t sock,
                                     const pjsip_host_port *a_name,
                                     unsigned async_cnt,
                                     pjsip_transport **p_transport)
{
    pj_pool_t *pool;
    struct udp_transport *tp;
    const char *format, *ipv6_quoteb = "", *ipv6_quotee = "";
    unsigned i;
    pj_status_t status;

    PJ_ASSERT_RETURN(endpt && sock!=PJ_INVALID_SOCKET && a_name && async_cnt>0,
                     PJ_EINVAL);

    /* Object name. */
    if (type & PJSIP_TRANSPORT_IPV6) {
        pj_in6_addr dummy6;

        format = "udpv6%p";
        /* We don't need to add quote if the transport type is IPv6, but
         * actually translated to IPv4.
         */
        if (pj_inet_pton(pj_AF_INET6(), &a_name->host, &dummy6)==PJ_SUCCESS) {
            ipv6_quoteb = "[";
            ipv6_quotee = "]";
        }
    } else {
        format = "udp%p";
    }

    /* Create pool. */
    pool = pjsip_endpt_create_pool(endpt, format, PJSIP_POOL_LEN_TRANSPORT, 
                                   PJSIP_POOL_INC_TRANSPORT);
    if (!pool)
        return PJ_ENOMEM;

    /* Create the UDP transport object. */
    tp = PJ_POOL_ZALLOC_T(pool, struct udp_transport);

    /* Save pool. */
    tp->base.pool = pool;

    pj_memcpy(tp->base.obj_name, pool->obj_name, PJ_MAX_OBJ_NAME);

    /* Init reference counter. */
    status = pj_atomic_create(pool, 0, &tp->base.ref_cnt);
    if (status != PJ_SUCCESS)
        goto on_error;

    /* Init lock. */
    status = pj_lock_create_recursive_mutex(pool, pool->obj_name, 
                                            &tp->base.lock);
    if (status != PJ_SUCCESS)
        goto on_error;

    /* Set type. */
    tp->base.key.type = type;

    /* Remote address is left zero (except the family) */
    tp->base.key.rem_addr.addr.sa_family = (pj_uint16_t)
        ((type & PJSIP_TRANSPORT_IPV6) ? pj_AF_INET6() : pj_AF_INET());

    /* Type name. */
    tp->base.type_name = "UDP";

    /* Transport flag */
    tp->base.flag = pjsip_transport_get_flag_from_type(type);


    /* Length of addressess. */
    tp->base.addr_len = sizeof(tp->base.local_addr);

    /* Init local address. */
    status = pj_sock_getsockname(sock, &tp->base.local_addr, 
                                 &tp->base.addr_len);
    if (status != PJ_SUCCESS)
        goto on_error;

    /* Init remote name. */
    if (type == PJSIP_TRANSPORT_UDP)
        tp->base.remote_name.host = pj_str("0.0.0.0");
    else
        tp->base.remote_name.host = pj_str("::0");
    tp->base.remote_name.port = 0;

    /* Init direction */
    tp->base.dir = PJSIP_TP_DIR_NONE;

    /* Set endpoint. */
    tp->base.endpt = endpt;

    /* Transport manager and timer will be initialized by tpmgr */

    /* Attach socket and assign name. */
    udp_set_socket(tp, sock, a_name);

    /* Register to ioqueue */
    status = register_to_ioqueue(tp);
    if (status != PJ_SUCCESS)
        goto on_error;

    /* Set functions. */
    tp->base.send_msg = &udp_send_msg;
    tp->base.do_shutdown = &udp_shutdown;
    tp->base.destroy = &udp_destroy;

    /* Register to transport manager. */
    tp->base.tpmgr = pjsip_endpt_get_tpmgr(endpt);
    status = pjsip_transport_register( tp->base.tpmgr, (pjsip_transport*)tp);
    if (status != PJ_SUCCESS)
        goto on_error;

    /* This is a permanent transport, so we initialize the ref count
     * to one so that transport manager won't destroy this transport
     * when there's no user!
     */
    pjsip_transport_add_ref(&tp->base);

    /* Create rdata and put it in the array. */
    tp->rdata_cnt = 0;
    tp->rdata = (pjsip_rx_data**)
                pj_pool_calloc(tp->base.pool, async_cnt, 
                               sizeof(pjsip_rx_data*));
    for (i=0; i<async_cnt; ++i) {
        pj_pool_t *rdata_pool = pjsip_endpt_create_pool(endpt, "rtd%p", 
                                                        PJSIP_POOL_RDATA_LEN,
                                                        PJSIP_POOL_RDATA_INC);
        if (!rdata_pool) {
            pj_atomic_set(tp->base.ref_cnt, 0);
            pjsip_transport_destroy(&tp->base);
            return PJ_ENOMEM;
        }

        init_rdata(tp, i, rdata_pool, NULL);
        tp->rdata_cnt++;
    }

    /* Start reading the ioqueue. */
    status = start_async_read(tp);
    if (status != PJ_SUCCESS) {
        pjsip_transport_destroy(&tp->base);
        return status;
    }

    /* Done. */
    if (p_transport)
        *p_transport = &tp->base;
    
    PJ_LOG(4,(tp->base.obj_name, 
              "SIP %s started, published address is %s%.*s%s:%d",
              pjsip_transport_get_type_desc((pjsip_transport_type_e)tp->base.key.type),
              ipv6_quoteb,
              (int)tp->base.local_name.host.slen,
              tp->base.local_name.host.ptr,
              ipv6_quotee,
              tp->base.local_name.port));

    return PJ_SUCCESS;

on_error:
    udp_destroy((pjsip_transport*)tp);
    return status;
}
  • start_async_read: 此函数直接表明意思,启动异步读取数据

start_async_read

start_async_read 函数的定义 pjsip/src/pjsip/sip_transport_udp.c,其中定义如下所示:

/* Start ioqueue asynchronous reading to all rdata */
static pj_status_t start_async_read(struct udp_transport *tp)
{
    int i;
    pj_status_t status;

    /* Start reading the ioqueue. */
    for (i=0; i<tp->rdata_cnt; ++i) {
        pj_ssize_t size;

        size = sizeof(tp->rdata[i]->pkt_info.packet);
        tp->rdata[i]->pkt_info.src_addr_len = sizeof(tp->rdata[i]->pkt_info.src_addr);
        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);
        if (status == PJ_SUCCESS) {
            pj_assert(!"Shouldn't happen because PJ_IOQUEUE_ALWAYS_ASYNC!");
            udp_on_read_complete(tp->key, &tp->rdata[i]->tp_info.op_key.op_key,
                                 size);
        } else if (status != PJ_EPENDING) {
            /* Error! */
            return status;
        }
    }

    return PJ_SUCCESS;
}
  • pj_ioqueue_recvfrom: 发起接受事件

由于这里发起了接受数据的事件,那么我们需要到 epoll 中去看接受这个事件的处理函数。这里我们暂时不关心 epoll,因而我直接贴出函数名字 ioqueue_dispatch_read_event

ioqueue_dispatch_read_event

ioqueue_dispatch_read_event 函数的定义位于 pjlib/src/pj/ioqueue_common_abs.c 中,其中内容如下所示:

pj_bool_t ioqueue_dispatch_read_event( pj_ioqueue_t *ioqueue,
                                       pj_ioqueue_key_t *h )
{
    pj_status_t rc;

    /* Try lock the key. */
    rc = pj_ioqueue_trylock_key(h);
    if (rc != PJ_SUCCESS) {
        return PJ_FALSE;
    }

    if (IS_CLOSING(h)) {
        pj_ioqueue_unlock_key(h);
        return PJ_TRUE;
    }

#   if PJ_HAS_TCP
    if (!pj_list_empty(&h->accept_list)) {

        struct accept_operation *accept_op;
        pj_bool_t has_lock;
        
        /* Get one accept operation from the list. */
        accept_op = h->accept_list.next;
        pj_list_erase(accept_op);
        accept_op->op = PJ_IOQUEUE_OP_NONE;

        /* Clear bit in fdset if there is no more pending accept */
        if (pj_list_empty(&h->accept_list))
            ioqueue_remove_from_set(ioqueue, h, READABLE_EVENT);

        rc=pj_sock_accept(h->fd, accept_op->accept_fd, 
                          accept_op->rmt_addr, accept_op->addrlen);
        if (rc==PJ_SUCCESS && accept_op->local_addr) {
            rc = pj_sock_getsockname(*accept_op->accept_fd, 
                                     accept_op->local_addr,
                                     accept_op->addrlen);
        }

        /* Unlock; from this point we don't need to hold key's mutex
         * (unless concurrency is disabled, which in this case we should
         * hold the mutex while calling the callback) */
        if (h->allow_concurrent) {
            /* concurrency may be changed while we're in the callback, so
             * save it to a flag.
             */
            has_lock = PJ_FALSE;
            pj_ioqueue_unlock_key(h);
            PJ_RACE_ME(5);
        } else {
            has_lock = PJ_TRUE;
        }

        /* Call callback. */
        if (h->cb.on_accept_complete && !IS_CLOSING(h)) {
            (*h->cb.on_accept_complete)(h, 
                                        (pj_ioqueue_op_key_t*)accept_op,
                                        *accept_op->accept_fd, rc);
        }

        if (has_lock) {
            pj_ioqueue_unlock_key(h);
        }
    }
    else
#   endif
    if (key_has_pending_read(h)) {
        struct read_operation *read_op;
        pj_ssize_t bytes_read;
        pj_bool_t has_lock;

        /* Get one pending read operation from the list. */
        read_op = h->read_list.next;
        pj_list_erase(read_op);

        /* Clear fdset if there is no pending read. */
        if (pj_list_empty(&h->read_list))
            ioqueue_remove_from_set(ioqueue, h, READABLE_EVENT);

        bytes_read = read_op->size;

        if (read_op->op == PJ_IOQUEUE_OP_RECV_FROM) {
            read_op->op = PJ_IOQUEUE_OP_NONE;
            rc = pj_sock_recvfrom(h->fd, read_op->buf, &bytes_read, 
                                  read_op->flags,
                                  read_op->rmt_addr, 
                                  read_op->rmt_addrlen);
        } else if (read_op->op == PJ_IOQUEUE_OP_RECV) {
            read_op->op = PJ_IOQUEUE_OP_NONE;
            rc = pj_sock_recv(h->fd, read_op->buf, &bytes_read, 
                              read_op->flags);
        } else {
            pj_assert(read_op->op == PJ_IOQUEUE_OP_READ);
            read_op->op = PJ_IOQUEUE_OP_NONE;
            /*
             * User has specified pj_ioqueue_read().
             * On Win32, we should do ReadFile(). But because we got
             * here because of select() anyway, user must have put a
             * socket descriptor on h->fd, which in this case we can
             * just call pj_sock_recv() instead of ReadFile().
             * On Unix, user may put a file in h->fd, so we'll have
             * to call read() here.
             * This may not compile on systems which doesn't have 
             * read(). That's why we only specify PJ_LINUX here so
             * that error is easier to catch.
             */
#           if defined(PJ_WIN32) && PJ_WIN32 != 0 || \
               defined(PJ_WIN64) && PJ_WIN64 != 0 || \
               defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0
                rc = pj_sock_recv(h->fd, read_op->buf, &bytes_read, 
                                  read_op->flags);
                //rc = ReadFile((HANDLE)h->fd, read_op->buf, read_op->size,
                //              &bytes_read, NULL);
#           elif (defined(PJ_HAS_UNISTD_H) && PJ_HAS_UNISTD_H != 0)
                bytes_read = read(h->fd, read_op->buf, bytes_read);
                rc = (bytes_read >= 0) ? PJ_SUCCESS : pj_get_os_error();
#           else
#               error "Implement read() for this platform!"
#           endif
        }
        
        if (rc != PJ_SUCCESS) {
#           if (defined(PJ_WIN32) && PJ_WIN32 != 0) || \
               (defined(PJ_WIN64) && PJ_WIN64 != 0) 
            /* On Win32, for UDP, WSAECONNRESET on the receive side 
             * indicates that previous sending has triggered ICMP Port 
             * Unreachable message.
             * But we wouldn't know at this point which one of previous 
             * key that has triggered the error, since UDP socket can
             * be shared!
             * So we'll just ignore it!
             */

            if (rc == PJ_STATUS_FROM_OS(WSAECONNRESET)) {
                //PJ_LOG(4,(THIS_FILE, 
                //          "Ignored ICMP port unreach. on key=%p", h));
            }
#           endif

            /* In any case we would report this to caller. */
            bytes_read = -rc;

#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && \
    PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT!=0
            /* Special treatment for dead UDP sockets here, see ticket #1107 */
            if (rc == PJ_STATUS_FROM_OS(ENOTCONN) && !IS_CLOSING(h) &&
                h->fd_type==pj_SOCK_DGRAM())
            {
                rc = replace_udp_sock(h);
                if (rc != PJ_SUCCESS) {
                    bytes_read = -rc;
                }
            }
#endif
        }

        /* Unlock; from this point we don't need to hold key's mutex
         * (unless concurrency is disabled, which in this case we should
         * hold the mutex while calling the callback) */
        if (h->allow_concurrent) {
            /* concurrency may be changed while we're in the callback, so
             * save it to a flag.
             */
            has_lock = PJ_FALSE;
            pj_ioqueue_unlock_key(h);
            PJ_RACE_ME(5);
        } else {
            has_lock = PJ_TRUE;
        }

        /* Call callback. */
        if (h->cb.on_read_complete && !IS_CLOSING(h)) {
            (*h->cb.on_read_complete)(h, 
                                      (pj_ioqueue_op_key_t*)read_op,
                                      bytes_read);
        }

        if (has_lock) {
            pj_ioqueue_unlock_key(h);
        }

    } else {
        /*
         * This is normal; execution may fall here when multiple threads
         * are signalled for the same event, but only one thread eventually
         * able to process the event.
         */
        pj_ioqueue_unlock_key(h);

        return PJ_FALSE;
    }

    return PJ_TRUE;
}
  • pj_sock_recv:从 socket 中收取响应的数据内容
  • on_read_complete: 读取数据成功后,回到读取成功

由于我们在前面已经多次讲述了 on_read_complete 这个函数的注册,那么这里我们直接跳转到 udp_on_read_complete

udp_on_read_complete

udp_on_read_complete 函数的定义位于 pjsip/src/pjsip/sip_transport_udp.c 中,其中内容如下所示:

static void udp_on_read_complete( pj_ioqueue_key_t *key, 
                                  pj_ioqueue_op_key_t *op_key, 
                                  pj_ssize_t bytes_read)
{
    /* See https://github.com/pjsip/pjproject/issues/1197 */
    enum { MAX_IMMEDIATE_PACKET = 50 };
    pjsip_rx_data_op_key *rdata_op_key = (pjsip_rx_data_op_key*) op_key;
    pjsip_rx_data *rdata = rdata_op_key->rdata;
    struct udp_transport *tp = (struct udp_transport*)rdata->tp_info.transport;
    int i;
    pj_status_t status;

    ++tp->read_loop_spin;

    /* Don't do anything if transport is closing. */
    if (tp->is_closing) {
        tp->is_closing++;
        goto on_return;
    }

    /* Don't do anything if transport is being paused. */
    if (tp->is_paused)
        goto on_return;

    if (-bytes_read == PJ_ESOCKETSTOP) {
#if 0
        /* Auto restart is disabled, see #2881 */
        --tp->read_loop_spin;
        /* Try to recover by restarting the transport. */
        PJ_LOG(4,(tp->base.obj_name, "Restarting SIP UDP transport"));
        status = pjsip_udp_transport_restart2(
                            &tp->base,
                            PJSIP_UDP_TRANSPORT_DESTROY_SOCKET,
                            PJ_INVALID_SOCKET,
                            &tp->base.local_addr,
                            &tp->base.local_name);

        if (status != PJ_SUCCESS) {
            PJ_PERROR(1,(THIS_FILE, status,
                         "Error restarting SIP UDP transport"));
        }
        return;
#else
        goto on_return;
#endif
    }

    /*
     * The idea of the loop is to process immediate data received by
     * pj_ioqueue_recvfrom(), as long as i < MAX_IMMEDIATE_PACKET. When
     * i is >= MAX_IMMEDIATE_PACKET, we force the recvfrom() operation to
     * complete asynchronously, to allow other sockets to get their data.
     */
    for (i=0;; ++i) {
        enum { MIN_SIZE = 32 };
        pj_uint32_t flags;

        /* Report the packet to transport manager. Only do so if packet size
         * is relatively big enough for a SIP packet.
         */
        if (bytes_read > MIN_SIZE) {
            pj_ssize_t size_eaten;
            const pj_sockaddr *src_addr = &rdata->pkt_info.src_addr;

            /* Init pkt_info part. */
            rdata->pkt_info.len = bytes_read;
            rdata->pkt_info.zero = 0;
            pj_gettimeofday(&rdata->pkt_info.timestamp);
            pj_sockaddr_print(src_addr, rdata->pkt_info.src_name,
                              sizeof(rdata->pkt_info.src_name), 0);
            rdata->pkt_info.src_port = pj_sockaddr_get_port(src_addr);

            size_eaten = 
                pjsip_tpmgr_receive_packet(rdata->tp_info.transport->tpmgr, 
                                           rdata);

            if (size_eaten < 0) {
                pj_assert(!"It shouldn't happen!");
                size_eaten = rdata->pkt_info.len;
            }

            /* Since this is UDP, the whole buffer is the message. */
            rdata->pkt_info.len = 0;

        } else if (bytes_read >= 0 && bytes_read <= MIN_SIZE) {

            /* TODO: */

        } else if (-bytes_read != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) &&
                   -bytes_read != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) && 
                   -bytes_read != PJ_STATUS_FROM_OS(OSERR_ECONNRESET)) 
        {

            /* Report error to endpoint. */
            PJSIP_ENDPT_LOG_ERROR((rdata->tp_info.transport->endpt,
                                   rdata->tp_info.transport->obj_name,
                                   (pj_status_t)-bytes_read, 
                                   "Warning: pj_ioqueue_recvfrom()"
                                   " callback error"));
        }

        if (i >= MAX_IMMEDIATE_PACKET) {
            /* Force ioqueue_recvfrom() to return PJ_EPENDING */
            flags = PJ_IOQUEUE_ALWAYS_ASYNC;
        } else {
            flags = 0;
        }

        /* Reset pool. 
         * Need to copy rdata fields to temp variable because they will
         * be invalid after pj_pool_reset().
         */
        {
            pj_pool_t *rdata_pool = rdata->tp_info.pool;
            struct udp_transport *rdata_tp ;
            unsigned rdata_index;

            rdata_tp = (struct udp_transport*)rdata->tp_info.transport;
            rdata_index = (unsigned)(unsigned long)(pj_ssize_t)
                          rdata->tp_info.tp_data;

            pj_pool_reset(rdata_pool);
            init_rdata(rdata_tp, rdata_index, rdata_pool, &rdata);

            /* Change some vars to point to new location after
             * pool reset.
             */
            op_key = &rdata->tp_info.op_key.op_key;
        }

        /* Only read next packet if transport is not being paused. This
         * check handles the case where transport is paused while endpoint
         * is still processing a SIP message.
         */
        if (tp->is_paused)
            break;

        /* Read next packet. */
        bytes_read = sizeof(rdata->pkt_info.packet);
        rdata->pkt_info.src_addr_len = sizeof(rdata->pkt_info.src_addr);
        status = pj_ioqueue_recvfrom(key, op_key, 
                                     rdata->pkt_info.packet,
                                     &bytes_read, flags,
                                     &rdata->pkt_info.src_addr, 
                                     &rdata->pkt_info.src_addr_len);

        if (status == PJ_SUCCESS) {
            /* Continue loop. */
            pj_assert(i < MAX_IMMEDIATE_PACKET);

        } else if (status == PJ_EPENDING) {
            break;

        } else if (status == PJ_ECANCELLED) {
            /* Socket is closing, quit loop */
            break;

        } else {

            if (i < MAX_IMMEDIATE_PACKET) {

                /* Report error to endpoint if this is not EWOULDBLOCK error.*/
                if (status != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) &&
                    status != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) && 
                    status != PJ_STATUS_FROM_OS(OSERR_ECONNRESET)) 
                {
                    PJSIP_ENDPT_LOG_ERROR((rdata->tp_info.transport->endpt,
                                           rdata->tp_info.transport->obj_name,
                                           status, 
                                           "Warning: pj_ioqueue_recvfrom"));
                }

                /* Continue loop. */
                bytes_read = 0;
            } else {
                /* This is fatal error.
                 * Ioqueue operation will stop for this transport!
                 */
                PJSIP_ENDPT_LOG_ERROR((rdata->tp_info.transport->endpt,
                                       rdata->tp_info.transport->obj_name,
                                       status, 
                                       "FATAL: pj_ioqueue_recvfrom() error, "
                                       "UDP transport stopping! Error"));
                break;
            }
        }
    }

on_return:
    --tp->read_loop_spin;
}
  • pjsip_tpmgr_receive_packet: 接受的数据传输出去并进行解析
  • pj_ioqueue_recvfrom: 为了让程序能够不断的去接收数据,这里再次发起一个事件。

总结:从接收数据的角度来说,从第一个 start_async_read 后,每次调用成功后都会继续发送事件 pj_ioqueue_recvfrom 用于继续获取数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值