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
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 结构体分配空间,并进行初始化:
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;
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 )
6. 初始化监听器,添加监听器到套接字
(待续。。。)
7.启动监听器
8.释放监听器对象。
9. 返回 ;