1. 函数原型:
#include<nng/nng.h>
int nng_listen(nng_socket s, const char *url, nng_listener *lp, int flags)
2. 手册说明(译)
nng_listen()函数创建一个新初始化的nng_listener对象,该对象与套接字s关联,并配置为在url指定的地址进行监听,并启动它。如果lp不为NULL,则新创建的监听器将存储在lp指定的位置。 监听器用于接受远程拨号器发起的链接。flag 暂时没有用到,但是留作将来使用。
这里先贴一下nng_listen的定义:
int nng_listen(nng_socket sid, const char *addr, nng_listener *lp, int flags)
{
int rv;
nni_sock *s;
nni_listener *l;
// 找到对应的 socket 对象
if ((rv = nni_sock_find(&s, sid.id)) != 0) {
return (rv);
}
// 创建一个新的监听器对象,并与 socket 绑定
if ((rv = nni_listener_create(&l, s, addr)) != 0) {
nni_sock_rele(s); // 释放 socket 对象
return (rv);
}
// 启动监听器
if ((rv = nni_listener_start(l, flags)) != 0) {
nni_listener_close(l); // 启动失败,关闭监听器
return (rv);
}
// 如果用户提供了 lp 参数,返回监听器的 ID
if (lp != NULL) {
nng_listener lid;
lid.id = nni_listener_id(l);
*lp = lid;
}
nni_listener_rele(l); // 释放监听器对象
return (rv);
}
展开分析:
通常我们调用函数的方式为: nng_listen(sock, "ipc:/aaa", NULL, 0) 整体来说,listen 动作是需要关联到套接字 s 上的,所以第一步是查找套接字,根据 sid.id 获取到结构体类型 nni_sock 的 s 的值。这里贴出nni_sock 的定义:
struct nni_socket {
nni_list_node s_node;
nni_mtx s_mx;
nni_cv s_cv;
nni_cv s_close_cv;
uint32_t s_id;
uint32_t s_flags;
unsigned s_ref; // protected by global lock
void *s_data; // Protocol private
size_t s_size;
nni_msgq *s_uwq; // Upper write queue
nni_msgq *s_urq; // Upper read queue
nni_proto_id s_self_id;
nni_proto_id s_peer_id;
nni_proto_pipe_ops s_pipe_ops;
nni_proto_sock_ops s_sock_ops;
nni_proto_ctx_ops s_ctx_ops;
// options
nni_duration s_sndtimeo; // send timeout
nni_duration s_rcvtimeo; // receive timeout
nni_duration s_reconn; // reconnect time
nni_duration s_reconnmax; // max reconnect time
size_t s_rcvmaxsz; // max receive size
nni_list s_options; // opts not handled by sock/proto
char s_name[64]; // socket name (legacy compat)
nni_list s_listeners; // active listeners
nni_list s_dialers; // active dialers
nni_list s_pipes; // active pipes
nni_list s_ctxs; // active contexts (protected by global sock_lk)
bool s_closing; // Socket is closing
bool s_closed; // Socket closed, protected by global lock
bool s_ctxwait; // Waiting for contexts to close.
nni_mtx s_pipe_cbs_mtx;
nni_sock_pipe_cb s_pipe_cbs[NNG_PIPE_EV_NUM];
};
查找到前面创建的套接字结构体之后,接下来会创建 listener 调用函数nni_listener_create() , 函数参数分别为
nni_listener **l : 二级指针,类型是& nni_listener *
nni_sock *s : 前面根据套接字 id 找到的套接字结构体
const char *url_str : 要监听的地址
nni_listener_create (nni_listener **lp, nni_sock *s, const char *url_str) 先不考虑异常导致失败的处理,该函数的处理流程如下:
1. 为url 分配空间并解析 url_str 到局部变量 url 中, 当我传入 url_str 为 “ipc:///tmp/aaa” 的时候,解析效果如下:
url->u_rawurl [ipc:///tmp/aaa] /*原始参数*/
url->u_scheme [ipc]
url->u_userinfo[(null)]
url->u_host [(null)]
url->u_hostname[(null)]
url->u_port [(null)]
url->u_path [/tmp/aaa]
url->u_query [(null)]
url->u_fragment[(null)]
url->u_requri [(null)]
2. 根据解析的 url 的 url->u_scheme 从全局变量 sp_tran_list 中查找传输层的信息,其中 sp_tran_list 在init 函数初始化。这里以ipc 模式为例,将会找到传输层的信息为:nni_sp_tran_sys_init(); 函数初始化的信息,也就是 nni_sp_ipc_register(); 函数进行的初始化。 这里查找的结果是 ipc_tran,下面是ipc_tran的结构体定义,含各变量及函数指针。
static nni_sp_dialer_ops ipc_dialer_ops = {
.d_init = ipc_ep_init_dialer,
.d_fini = ipc_ep_fini,
.d_connect = ipc_ep_connect,
.d_close = ipc_ep_close,
.d_getopt = ipc_dialer_get,
.d_setopt = ipc_dialer_set,
};
static nni_sp_listener_ops ipc_listener_ops = {
.l_init = ipc_ep_init_listener,
.l_fini = ipc_ep_fini,
.l_bind = ipc_ep_bind,
.l_accept = ipc_ep_accept,
.l_close = ipc_ep_close,
.l_getopt = ipc_listener_get,
.l_setopt = ipc_listener_set,
};
static nni_sp_pipe_ops ipc_tran_pipe_ops = {
.p_init = ipc_pipe_init,
.p_fini = ipc_pipe_fini,
.p_stop = ipc_pipe_stop,
.p_send = ipc_pipe_send,
.p_recv = ipc_pipe_recv,
.p_close = ipc_pipe_close,
.p_peer = ipc_pipe_peer,
.p_getopt = ipc_pipe_get,
};
static nni_sp_tran ipc_tran = {
.tran_scheme = "ipc",
.tran_dialer = &ipc_dialer_ops,
.tran_listener = &ipc_listener_ops,
.tran_pipe = &ipc_tran_pipe_ops,
.tran_init = ipc_tran_init,
.tran_fini = ipc_tran_fini,
};
3. 为 nni_listener 结构体分配空间,并进行初始化:
l = NNI_ALLOC_STRUCT(l))
4. 初始化异步操作I/O对象,对 &l->l_acc_aio 和 &l->l_tmo_aio 分别进行初始化,除了简单的赋值,还调用了函数 nni_task_init 对 &l->l_acc_aio->task 进行初始化。初始化后的结果是:
//初始化监听器结构
l->l_url = url;
l->l_closed = false;
l->l_data = NULL;
l->l_ref = 1;
l->l_sock = s;
l->l_tran = tran;
l->l_ops = *tran->tran_listener; 这个赋值操作等价于:
l->l_ops = &ipc_tran->tran_listener; 结合transport的初始化,赋值后的值:
l->l_ops = &ipc_listener_ops
l->l_acc_aio->a_expire = NNI_TIME_NEVER;
l->l_acc_aio->l_timeout = NNG_DURATION_INFINITE;
l->l_acc_aio->a_expire_q = nni_aio_expire_q_list[随机一个];
&l->l_acc_aio->task->task_prep = false;
&l->l_acc_aio->task->task_busy = 0;
&l->l_acc_aio->task->task_cb = listener_accept_cb;
&l->l_acc_aio->task->arg = l;
&l->l_acc_aio->task->task_tq = nni_taskq_systq;
l->l_tmo_aio->a_expire = NNI_TIME_NEVER;
l->l_tmo_aio->l_timeout = NNG_DURATION_INFINITE;
l->l_acc_aio->a_expire_q = nni_aio_expire_q_list[随机一个];
&l->l_tmo_aio->task->task_prep = false;
&l->l_tmo_aio->task->task_busy = 0;
&l->l_tmo_aio->task->task_cb = listener_accept_cb;
&l->l_tmo_aio->task->arg = l;
&l->l_tmo_aio->task->task_tq = nni_taskq_systq;
5. 分配监听器ID,调用函数 nni_id_alloc32(&listeners, &l->id, l ),调用函数对 l->l_id。
调用 l->l_ops.l_init(&l->l_data, url, l) 对 l->data进行初始化,这里的l->data 是void *类型。 从上面的初始化看(l->l_ops = *tran->tran_listener;),l_init 其实就是函数 ipc_ep_init_listener, 也就是调用函数ipc_ep_init_listener(l->l_data, url, l ). 具体操作就是定义变量 ipc_ep *ep ; 然后对ep 进行初始化,最后把初始化之后的地址给到 l->l_data .
nng_stream_listener_alloc_url(&ep->listener, url) 在这里查找对应的scheme (ipc模式为字符串“ipc”)调用了 stream_drivers[] 数组里ipc对应的函数nni_ipc_listener_alloc, 定义了变量ipc_listener *l ; 对 l 做了初始化,并把地址给到 ep->listener;供后续的使用。这里给出一个简单的初始化后的示意图:
上面操作完成后,调用函数nni_sock_add_listener(s, l) 将创建的listener 信息和socket关联,也就是放在了s 的结构体中(成员中的一个listener的链表)。
6. 执行nni_listener_start(l, flag) 启动监听
在这里把监听和accept 过程拟合在了一起。
依次执行:l->l_ops.l_bind(l->l_data);这里根据前面的初始化,l_bind就是ipc_ep_bind(void *arg) ,用来绑定监听的信息,这里的arg 参数是 l->l_data(实际类型是前面初始化之后的值,类型是 ipc_ep *ep)。ipc_ep_bind(void *arg) 中调用了nng_stream_listener_listen(ep->listener); 在nng_stream_listener_listen中调用了 l->sl_listen(l)
(这里的参数 l 为 ep->listener)根据前面对l->data的初始化, sl_listen指针最终指向的是函数ipc_listener_listen(void *arg),在这里根据前面初始化的协议族,创建套接字,绑定地址。
定义 nni_posix_pfd * pfd; 并对pfd 使用函数 nni_posix_pfd_init(&pfd, fd)初始化,这里的fd就是使用标准函数nni_posix_pfd_init(&pfd, fd) 创建的。
nni_posix_pfd_init中,把fd 赋值给pfd的成员变量,并把 fd添加到epoll实例中epoll_ctl(pq->epfd, EPOLL_CTL_ADD, fd, &ev), 并把pfd的成员变量赋值为pq = &nni_posix_global_pollq;
设置pfd的回调函数 nni_posix_pfd_set_cb(pfd, ipc_listener_cb, l);
最后把pfd的值给到ipc_listener l 的成员变量。
l->pfd = pfd;
l->started = true;
l->path = nni_strdup(l->sa.s_ipc.sa_path ;
9.nni_listener_rele(l);
这里把前面创建的listener放在全局链表listener_reap_list上,等条件满足时回收上面的资源并返回,nni_listener_reap(l)
10. END ;