[!note]
本篇文档的所有分析过程均基于如下软件版本:
概述
本篇文档主要介绍了 netopeer2 的主要流程和构造。
netopeer2 的主要架构已经在 官方文档 中描述得非常清楚,本文只是记录了对官方文档及源码的一些学习和思考过程。
主要流程
初始化
netopeer2 的 main 函数主要过程如下:
- 设置 singal handler(在接收到终止信号时通知各 worker 结束循环)
- 解析参数。参数介绍见 print_usage 函数
- 创建 pid 文件
- 设置 libnetconf2、libyang 和 sysrepo 的 log 回调
- 初始化 netconf 服务器(server_init),主要包括:
- 初始化 sysrepo 连接,此步骤消耗大量内存,内存量与 yang 模型总大小强相关
- 初始化 libnetconf2
- 订阅 sysrepo 事件(包括 rpc 和数据变更事件)
- 启动多个 worker 线程以监听并处理连接
- 主线程也作为一个 worker 开始工作
这里有个巧思,很多程序都会启动 worker_thread 作为业务处理线程,而启动后主线程就无事可做了,出于 idle 状态。而 netopeer2 在主线程也调用了 worker 函数,即它本身也作为一个 worker 线程,减少了创建一个线程的开销。
由于设置了 singal handler,当接收到终止信号时,各 worker 线程(包括主线程)会退出 worker 循环,不再监听连接。
主线程退出 worker 循环后,会等待所有 worker 线程退出并清理相关资源。
worker 线程
worker 线程是一个 loop 结构,其主体循环执行一系列操作以创建、跟踪并处理会话。其主体内容如子章节所述。
worker 线程通过一个原子变量 loop_continue 控制 是否继续进行循环,如果原子变量为 0,则结束循环并清理资源后退出。原子变量置 0 后 loop 退出的最长时间 约为 200ms。
创建新会话
在 worker_thread 的循环中,首先尝试接受一个新的 netconf session(会话)。会话指的是一个客户端到服务端的连接,netconf 使用 session 维持连接状态和上下文,并通过 sesssion 进行数据操作、事务管理、事件订阅等功能。
创建新 session 的功能由 server_accept_session 函数实现,其主要流程如下:
- 先 检查 是否存在监听中的 endpoint(例如 ssh、tld、unix socket 等,由 nc_server_add_endpt 添加)
- 如果不存在监听中的 endpoint,流程直接结束
- 从 sysrepo 连接中调用 sr_acquire_context 获取 libyang context
- 在这个过程中(sr_acquire_context -> sr_lycc_lock),sysrepo 会 检查 当前连接的 content_id 是否与 sysrepo main shm 中的 content_id 一致(即检查两者的内容版本是否一致)。
- 如果不一致,则重新加载新的 yang modules 作为新的 libyang context。
- 这一步主要是方式 sysrepo connection 创建之后 yang modules 由变动的情况
- 获取 ly_ctx 之后,netopeer2 调用 nc_accept 尝试获取一个会话(netconf session),其主要过程如下:
- 如果成功创建一个正常的 session,则调用 np2srv_new_session_cb 初始化 netopeer2 callback 信息,包括:
- 将该 session 通过 ncm_session_add 加入 监控列表 stats.sessions 中
- 从 sysrepo connection 中 创建 一个 session 并 记录 到 netconf session 上。这一步的目的是让每个 netconf session 可以独立地利用 sysrepo session 进行消息订阅、上锁等操作
- 将新建的 session 通过 nc_ps_add_session 添加 到 netconf 会话池中以便跟踪
- 向 sysrepo 发送 netconf notification 通告 新连接已创立,并表达连接者的身份
- 经过上面的步骤后,新会话已完成连接、握手、初始化等环境,进入可用状态
会话处理
在 worker_thread 的每轮 loop 中,都会通过 调用 nc_ps_poll 处理现有的会话。会话的处理过程大致如下:
- 逐个 检查现有会话,通过 调用 nc_ps_poll_session_io 获取会话状态(是否有新消息、连接是否已经关闭……)
- 如果 存在已经有数据的会话,则通过 调用 nc_server_recv_rpc_io 从底层信道(SSH、TLS、unix socket)中 接收数据 并尝试 解析 为 netconf 消息
- 如果接收到的 netconf 消息合法无误,则通过 调用 nc_server_send_reply_io 完成消息处理并回复请求
其中,消息处理功能在 nc_server_send_reply_io 中实现。其大致流程如下:
- 从消息中 找到 操作类型对应的 lysc_node 节点
- 如果 lysc_node 节点没有指定自定义的 callback,则调用 global RPC callback(一般指向 np2srv_rpc_cb)
- callback 中主要通过 调用 sr_rpc_send_tree 完成消息处理
- 否则,调用 lysc_node 节点自定义的 callback 处理消息
- 自定义 callback 消息目前只默认设置了这些回调:
- get-schema:指向 nc_clb_default_get_schema,获取结构信息
- close-session:指向 nc_clb_default_close_session,关闭会话
- 自定义 callback 消息目前只默认设置了这些回调:
- 最后,将 callback 处理后的消息回复给客户端
会话处理过程在官方文档中亦有记述,如下图: