本文主要对 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 用于继续获取数据。