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 ;

<think>好的,我现在需要帮助用户理解CMake配置中NNG_ENABLE_TLS选项的条件逻辑以及相关设置的作用。首先,我得回忆一下CMake的基本语法和工作原理,特别是条件语句和选项的定义。用户提到了几个宏,比如NNG_TRANSPORT_MQTT_BROKER_TLS、NNG_TRANSPORT_MQTT_BROKER_WSS,还有DNNG_SUPP_TLS,这些可能和TLS支持有关。 首先,NNG_ENABLE_TLS应该是一个CMake选项,用户可以通过设置ON或OFF来决定是否启用TLS支持。当这个选项启用时,CMake会检查系统是否安装了必要的TLS库,比如OpenSSL或者mbed TLS。如果找到这些依赖库,CMake会定义相关的宏,比如DNNG_SUPP_TLS,这样在代码中就可以通过预处理器指令来包含TLS相关的代码。 接下来,用户提到的NNG_TRANSPORT_MQTT_BROKER_TLS和NNG_TRANSPORT_MQTT_BROKER_WSS这两个宏可能分别对应MQTT协议的TLS加密传输和WebSocket Secure(WSS)传输。这两个宏的定义可能依赖于NNG_ENABLE_TLS的状态,也就是说,只有当TLS被启用时,这些传输层的安全选项才会被激活。因此,在CMake文件中可能会有条件判断,如果NNG_ENABLE_TLS为ON,并且找到了相应的库,才会定义这些宏。 另外,DNNG_SUPP_TLS这个宏可能在代码中用于条件编译,控制TLS相关功能的编译与否。用户可能需要知道这些宏是如何在CMake中设置的,以及它们之间的依赖关系。例如,如果用户希望启用MQTT over TLS,那么需要同时启用NNG_ENABLE_TLS和NNG_TRANSPORT_MQTT_BROKER_TLS。 我还需要考虑用户可能的实际应用场景,比如他们在配置项目时遇到了依赖库未找到的问题,或者不理解各个选项之间的关联。可能需要解释如何正确配置CMake选项,以及如何处理依赖库的路径问题。此外,用户可能想知道这些选项对最终生成的可执行文件或库的影响,比如是否包含TLS功能,或者如何通过宏定义来开启特定功能。 最后,根据用户的要求,生成相关问题时要覆盖配置逻辑、依赖关系、实际应用和问题排查等方面,确保问题能够帮助用户更全面地理解相关配置的作用和使用方法。</think>### CMake中NNG_ENABLE_TLS及相关配置解析 #### 1. **NNG_ENABLE_TLS的作用** `NNG_ENABLE_TLS`是用于控制NNG库是否启用TLS(Transport Layer Security)的CMake编译选项。当设置为`ON`时: - 系统会尝试检测依赖的TLS库(如OpenSSL或mbed TLS) - 若检测成功,会定义宏`DNNG_SUPP_TLS`,表示支持TLS功能 - 同时激活与TLS相关的传输层配置,例如`NNG_TRANSPORT_MQTT_BROKER_TLS`和`NNG_TRANSPORT_MQTT_BROKER_WSS` 典型条件逻辑代码片段: ```cmake if(NNG_ENABLE_TLS) find_package(OpenSSL REQUIRED) if(OPENSSL_FOUND) add_definitions(-DDNNG_SUPP_TLS) set(NNG_TRANSPORT_MQTT_BROKER_TLS 1) set(NNG_TRANSPORT_MQTT_BROKER_WSS 1) endif() endif() ``` #### 2. **相关宏定义解析** - `DNNG_SUPP_TLS`:全局TLS支持标志,影响所有依赖TLS的模块编译 - `NNG_TRANSPORT_MQTT_BROKER_TLS`:启用MQTT协议基于TLS的加密传输(通常对应`mqtt-tls://`协议头) - `NNG_TRANSPORT_MQTT_BROKER_WSS`:启用WebSocket Secure传输支持(对应`wss://`协议头) #### 3. **依赖关系** - 必须同时满足: $$NNG\_TRANSPORT\_XXX\_TLS \subseteq DNNG\_SUPP\_TLS \subseteq NNG\_ENABLE\_TLS$$ - TLS传输层实现需要底层库支持,例如OpenSSL提供以下关键功能: - TLS握手协议 - 对称加密算法(如AES) - 证书验证机制[^1] #### 4. **典型应用场景** ```cmake # 启用TLS和MQTT相关传输 set(NNG_ENABLE_TLS ON CACHE BOOL "Enable TLS support") set(NNG_TRANSPORT_MQTT_BROKER ON) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值