RTU/mimo/工程/libcomm模块分析.md

8.2 KiB
Raw Permalink Blame History

libcomm 模块分析

日期: 2026-06-12
基于源码: src/public/libcomm/8个文件约2200行


1. 模块定位

libcomm 是 RTU 的统一通信抽象层,位于公共库层。它将 TCP/UART/UDP 三种通信方式包装为统一的 C 接口,上层模块只需通过 comm_id 句柄操作连接、收发数据,不感知底层传输差异。

上层com_channel / icp67
    ↓ comm_create / comm_send / comm_recv_register
libcomm本模块
    ↓ dispatch by CommType
TCP client / TCP server / UART / UDP client / UDP server

2. 架构设计:工厂模式 + 虚函数表

2.1 统一基类 stru_comm

typedef struct {
    CommType type;
    CommInit init;         // no / ok
    CommDebugShow debug_show;  // on / off
    void *p_comm;          // 指向具体子类stru_comm_tcp/uart/udp
    int (*comm_connect)(int id);
    int (*comm_disconnect)(int id);
    int (*comm_run)(int id);
    int (*comm_state_register)(int id, comm_state_cb cb);
    int (*comm_recv_register)(int id, comm_recv_cb cb);
    int (*comm_send)(int id, const char *data, uint16_t len);
} stru_comm;

2.2 子类TCP / UART / UDP

每种通信方式有自己的结构体,包含:

结构体 特有字段 函数指针表
stru_comm_tcp sockfd, sock_listen_fd, client_fd[16], p_para tcp_client_connect / tcp_server_connect / tcp_send / tcp_close
stru_comm_uart uart_fd, p_para uart_connect / uart_send / uart_close
stru_comm_udp sockfd, p_para udp_client_connect / udp_server_connect / udp_send / udp_close

2.3 注册与管理

创建时分配 *_create 填充函数指针表 → comm_createCommType 分派 → 全局 g_comm_mapmap<int, stru_comm>

std::map<int, stru_comm> g_comm_map;
static int g_comm_id = 0;

所有后续操作通过 comm_id 查找 g_comm_map,再按 type 向下转型调用具体子类的函数指针。

3. TCP 实现comm_tcp.cpp640行

3.1 客户端模式(tcp_client_connect

while(1):
  socket() → connect(非阻塞) → select(3秒超时) → SO_ERROR 检查
  → tcp_open(SO_REUSEADDR + TCP keepalive)
  → 通知 state_cb(connected)
  
  while(sockfd 有效):
    select(sockfd, 100ms超时)
    → recv → recv_cb(id, fd, data, len)
    → len <= 0 → close → 通知 state_cb(disconnected) → 外层循环重新连接

特点: 自动断线重连5秒等待单连接模型非阻塞 connect 避免永久阻塞。

3.2 服务端模式(tcp_server_connect

tcp_listen → socket + bind + listen
while(1):
  select(listen_fd + client_fd[]) 
  → listen_fd 可读 → accept → 分配 client_fd[16] 空闲槽位
  → client_fd 可读 → recv → recv_cb
  → recv <= 0 → close + 通知 disconnected + 释放槽位

特点: 最多 16 个并发客户端select 阻塞等待(无超时),每个客户端 fd 分配独立槽位。send 失败时自动关闭对应连接。

3.3 Keepalive 配置

SO_KEEPALIVE + TCP_KEEPIDLE(30s) + TCP_KEEPINTVL(5s) + TCP_KEEPCNT(3)
// 30秒无活动 → 开始探测 → 每5秒一次 → 3次失败断开 → 最长75秒检测断线

3.4 send 实现

send(fd, data, len, MSG_NOSIGNAL)
// 失败时: 关闭 fd → state_cb(disconnected) → 自动触发重连

4. UART 实现comm_uart.cpp692行

4.1 初始化流程

uart_connect:
   open(device, O_RDWR | O_NOCTTY)
   tcgetattr + 配置波特率/数据位/停止位/校验位
   原始模式: ~ICANON, ~ECHO
   超时: VTIME=1(100ms), VMIN=0

4.2 波特率支持

600 / 1200 / 2400 / 4800 / 9600 / 19200 / 38400 / 57600 / 115200 / 500000 / 1M / 2.5M

4.3 运行时循环

while(1):
  read(uart_fd, buf, 1024)
   len > 0  recv_cb(id, fd, buf, len)
   uart_sleep(10ms)

当前实现: 逐次 read → 逐次回调不做帧拼装。注释掉的旧版本有超时分帧逻辑20ms 空闲检测 + 动态帧缓冲区),但被废弃。

4.4 uart_send

write(uart_fd, tx, tx_len)

5. UDP 实现comm_udp.cpp298行

5.1 客户端

socket(SOCK_DGRAM)  connect  remote:port  state_cb(connected)  udp_run

5.2 服务端

socket  bind local:port  state_cb(connected)  udp_run

5.3 udp_run 接收循环

while(1):
  recvfrom(sockfd, buf, 2048)
   recv_cb(id, sockfd, buf, len)
   出错时仅 continue,不触发重连

5.4 send

sendto(fd, data, len, remote_ip:remote_port)
// 目标和 recv 使用相同参数,服务端回包无需知道对端地址

6. fd 诊断工具comm.cpp 尾部)

get_fd_type(fd)

通过 fstat + TIOCGSERIAL 判断 fd 类型socket / serial / pipe / file。

get_fd_info(fd, info)

根据类型填充详细信息:

  • socket: getsockname + getpeername → local/remote IP + port
  • serial: readlink /proc/self/fd/N → 设备路径 + tcgetattr → 波特率/数据位/停止位/校验
  • file: fstat → 文件大小 + readlink → 路径

7. 调试支持

enum CommDebugShow { off, on };

static void comm_debug_show(str, dir, data, len):
   方向前缀(rx/tx + 颜色(绿色)
    64 字节换行的 hex dump

8. 与 icp67 / com_channel 的集成

com_channel.cpp 通过 myComm.h 定义 stru_tcp_parastru_uart_para 等参数结构体,调用 comm_create 创建通道实例,注册 comm_recv_registercomm_state_register 回调。收到的数据进入 icp67 帧解码。


9. 优点

优点 说明
统一接口 三种传输方式一视同仁,上层看不到 socket fd 和串口 fd 的区别
自动重连 TCP 客户端断线后带 5 秒退避自动重连,无需上层管理
调试开关 配置级 hex dump + 彩色方向标识,定位通信问题极快
fd 诊断 get_fd_info 可以运行时查看任何 fd 的完整信息(本地/远端 IP、串口参数等
TCP keepalive 配置了完整的心跳参数30s idle + 3 次探测),能在 75 秒内检测 TCP 半开连接
虚函数表模式 通过函数指针表实现多态,避免 switch/if-else 类型检查
服务端多连接 TCP server 支持最多 16 个并发客户端,每个客户端独立 fd
UART 参数完整 波特率覆盖 600~2.5M,支持奇偶校验配置

10. 缺点

缺点 严重度 说明
TCP 客户端空轮询 已修复 (2026-06-15) — select 去掉 100ms 超时,改为永久阻塞,零 CPU 空转
stru_comm 冗余函数指针 已修复 (2026-06-15) — 删除 stru_comm 中从未使用的 6 个函数指针字段comm_connect/comm_disconnect/comm_run/comm_state_register/comm_recv_register/comm_send
UART send 未完整实现 已修复 (2026-06-15) — 重写 comm_uart.cpp恢复原始模式~ICANON/~ECHO/~OPOST、VTIME=1 读超时、tcflush+tcdrain 完整发送
无内存释放 已修复 (2026-06-15) — 新增 comm_destroy(id) API释放 p_comm 内存并从 g_comm_map 移除
TCP 服务端阻塞 select select 无超时,服务端空闲时线程永久阻塞,若需同时处理定时任务则无法在同一线程
UDP 接收无超时 已修复 (2026-06-15)udp_runrecvfrom 前加 select 1s 超时,超时时 continue 继续循环
UDP close 回调 fd 错误 已修复 (2026-06-15)udp_close 先保存 fdclose,回调传入正确的旧 fd
void 类型擦除* p_commvoid* 再强转为具体类型,编译器无法检测类型错误
uart_connect 直接阻塞 串口打开和配置在工作线程的 while(1) 循环中,没有与上层消息分发解耦
TCP client_fd 槽位管理粗糙 最多 16 个客户端,超出直接拒绝,没有等待队列或优雅降级
无流量控制 send 没有检查 socket 缓冲区余量,高负载下可能 EAGAIN
日志宏不统一 混用 LOG_EC++ 风格)和 MY_LOG_E,部分日志无模块前缀