# 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` ```cpp 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_create` 按 `CommType` 分派 → 全局 `g_comm_map`(map): ```cpp std::map g_comm_map; static int g_comm_id = 0; ``` 所有后续操作通过 `comm_id` 查找 `g_comm_map`,再按 `type` 向下转型调用具体子类的函数指针。 ## 3. TCP 实现(comm_tcp.cpp,640行) ### 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 配置 ```cpp SO_KEEPALIVE + TCP_KEEPIDLE(30s) + TCP_KEEPINTVL(5s) + TCP_KEEPCNT(3) // 30秒无活动 → 开始探测 → 每5秒一次 → 3次失败断开 → 最长75秒检测断线 ``` ### 3.4 send 实现 ```cpp send(fd, data, len, MSG_NOSIGNAL) // 失败时: 关闭 fd → state_cb(disconnected) → 自动触发重连 ``` ## 4. UART 实现(comm_uart.cpp,692行) ### 4.1 初始化流程 ```cpp 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 运行时循环 ```cpp while(1): read(uart_fd, buf, 1024) → len > 0 → recv_cb(id, fd, buf, len) → uart_sleep(10ms) ``` **当前实现**: 逐次 read → 逐次回调,不做帧拼装。注释掉的旧版本有超时分帧逻辑(20ms 空闲检测 + 动态帧缓冲区),但被废弃。 ### 4.4 uart_send ```cpp write(uart_fd, tx, tx_len) ``` ## 5. UDP 实现(comm_udp.cpp,298行) ### 5.1 客户端 ```cpp socket(SOCK_DGRAM) → connect 到 remote:port → state_cb(connected) → udp_run ``` ### 5.2 服务端 ```cpp socket → bind local:port → state_cb(connected) → udp_run ``` ### 5.3 `udp_run` 接收循环 ```cpp while(1): recvfrom(sockfd, buf, 2048) → recv_cb(id, sockfd, buf, len) → 出错时仅 continue,不触发重连 ``` ### 5.4 send ```cpp 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. 调试支持 ```cpp 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_para`、`stru_uart_para` 等参数结构体,调用 `comm_create` 创建通道实例,注册 `comm_recv_register` 和 `comm_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_run` 中 `recvfrom` 前加 `select` 1s 超时,超时时 `continue` 继续循环 | | **UDP close 回调 fd 错误** | 低 | ✅ **已修复 (2026-06-15)** — `udp_close` 先保存 `fd` 再 `close`,回调传入正确的旧 fd | | **void* 类型擦除** | 中 | `p_comm` 是 `void*` 再强转为具体类型,编译器无法检测类型错误 | | **uart_connect 直接阻塞** | 低 | 串口打开和配置在工作线程的 `while(1)` 循环中,没有与上层消息分发解耦 | | **TCP client_fd 槽位管理粗糙** | 低 | 最多 16 个客户端,超出直接拒绝,没有等待队列或优雅降级 | | **无流量控制** | 低 | send 没有检查 socket 缓冲区余量,高负载下可能 EAGAIN | | **日志宏不统一** | 低 | 混用 `LOG_E`(C++ 风格)和 `MY_LOG_E`,部分日志无模块前缀 |