nng协议nng_listen函数执行过程

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 ;

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值