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

226 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<int, stru_comm>
```cpp
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 配置
```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.cpp692行
### 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.cpp298行
### 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`,部分日志无模块前缀 |