This commit is contained in:
coco
2026-07-03 16:05:30 +08:00
commit df489d5640
1101 changed files with 779140 additions and 0 deletions
@@ -0,0 +1,330 @@
# 串口通信
<cite>
**本文引用的文件列表**
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp)
- [SComPort.h](file://h/SComPort.h)
- [Constant.h](file://h/Constant.h)
- [MainFrm.cpp](file://cpp/Views/MainFrm.cpp)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文围绕 SComPort 类对 Windows 串口通信进行系统化技术文档化,重点覆盖:
- 串口初始化流程:CreateFile 创建句柄、SetupComm 配置缓冲区、SetCommState 设置波特率(115200)与数据格式(8位数据位、无校验、1位停止位)
- 异步 I/O 操作中 OVERLAPPED 结构的应用,以及 ReadFile/WriteFile 与 GetOverlappedResult 的配合实现非阻塞读写
- ClearCommError 和 PurgeComm 在错误处理与缓冲区清理中的作用
- 基于事件驱动的通信模式:EV_RXCHAR 接收字符事件与 EV_BREAK 断线检测的实现原理
- 串口打开、数据收发、异常处理与资源释放的完整调用示例
- 多线程环境下线程安全策略与注意事项
## 项目结构
SComPort 类位于工具模块中,提供串口底层封装与上层命令交互能力;相关常量定义位于公共头文件;UI 层通过消息回调处理断线事件。
```mermaid
graph TB
subgraph "工具层"
SComPort["SComPort 类<br/>cpp/Tools/SComPort.cpp"]
SComPortWinXP["SComPort 兼容实现<br/>cpp/Tools/SComPort_winxp.cpp"]
Header["头文件声明<br/>h/SComPort.h"]
Const["常量定义<br/>h/Constant.h"]
end
subgraph "界面层"
MainFrm["主窗口消息处理<br/>cpp/Views/MainFrm.cpp"]
end
SComPort --> Header
SComPort --> Const
SComPortWinXP --> Header
SComPortWinXP --> Const
MainFrm --> |"WM_BREAKLINE"| SComPort
```
图表来源
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L126-L223)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L126-L221)
- [SComPort.h](file://h/SComPort.h#L14-L71)
- [Constant.h](file://h/Constant.h#L224-L232)
- [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328)
章节来源
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L126-L223)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L126-L221)
- [SComPort.h](file://h/SComPort.h#L14-L71)
- [Constant.h](file://h/Constant.h#L224-L232)
- [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328)
## 核心组件
- SComPort 类:封装串口打开、配置、读写、Zmodem 收发、断线检测、日志与缓冲区清理等能力
- 常量定义:READBUFFER_SIZE、WRITEBUFFER_SIZE、COMM_TIMEOUT 等
- 主窗口消息处理:接收 WM_BREAKLINE 并关闭对应串口、更新设备状态
章节来源
- [SComPort.h](file://h/SComPort.h#L14-L71)
- [Constant.h](file://h/Constant.h#L224-L232)
- [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328)
## 架构总览
SComPort 采用 Windows 异步串口 I/O 模式,使用 OVERLAPPED 结构与事件对象实现非阻塞读写;通过 SetCommMask 注册事件掩码,结合 WaitCommEvent 实现事件驱动的接收路径;ClearCommError 用于查询错误与队列长度;PurgeComm 用于清理发送/接收缓冲区;断线检测通过独立线程轮询端口是否存在并发送 WM_BREAKLINE 给 UI。
```mermaid
sequenceDiagram
participant UI as "UI/调用方"
participant Port as "SComPort"
participant WinIO as "Windows 串口API"
participant Mask as "事件掩码(EV_* )"
participant Thread as "断线检测线程"
UI->>Port : 打开串口(OpenComm)
Port->>WinIO : CreateFile(异步标志)
Port->>WinIO : SetupComm(读写缓冲区大小)
Port->>WinIO : Get/SetCommState(波特率115200/8N1)
Port->>WinIO : SetCommMask(EV_RXCHAR/EV_BREAK/...)
Note over Port,WinIO : 初始化完成
UI->>Port : 发送数据(SendDataDirectly)
Port->>WinIO : WriteFile(Overlapped)
WinIO-->>Port : ERROR_IO_PENDING
Port->>WinIO : WaitForSingleObject(Overlapped.hEvent)
WinIO-->>Port : 完成事件
Port->>WinIO : GetOverlappedResult(获取字节数)
UI->>Port : 接收数据(ReceiveDataDirectly)
Port->>WinIO : WaitCommEvent(EV_RXCHAR)
WinIO-->>Port : 事件触发
Port->>WinIO : ClearCommError(查询InQue)
Port->>WinIO : ReadFile(Overlapped)
WinIO-->>Port : ERROR_IO_PENDING
Port->>WinIO : WaitForSingleObject(Overlapped.hEvent)
WinIO-->>Port : 完成事件
Port->>WinIO : GetOverlappedResult(获取字节数)
Thread->>WinIO : CreateFile(轮询)
WinIO-->>Thread : ERROR_FILE_NOT_FOUND
Thread->>UI : SendMessage(WM_BREAKLINE)
UI->>Port : CloseComm()
```
图表来源
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L156-L223)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L306-L401)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L527-L576)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L286-L380)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L382-L420)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L560-L607)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L746-L779)
- [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328)
## 详细组件分析
### 串口初始化流程
- 句柄创建:使用 CreateFile 打开端口,启用 FILE_FLAG_OVERLAPPED 以支持异步 I/O
- 缓冲区配置:SetupComm 设置读写缓冲区大小(由常量 READBUFFER_SIZE/WRITEBUFFER_SIZE 决定)
- 参数设置:GetCommState 获取当前 DCB,随后设置 BaudRate=115200、ByteSize=8、Parity=NOPARITY、StopBits=ONESTOPBIT,并调用 SetCommState 应用
- 事件掩码:SetCommMask 注册 EV_BREAK、EV_CTS、EV_DSR、EV_ERR、EV_RING、EV_RLSD、EV_RXCHAR、EV_RXFLAG、EV_TXEMPTY 等事件
章节来源
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L156-L223)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L155-L196)
- [Constant.h](file://h/Constant.h#L224-L226)
### 异步 I/O 与 OVERLAPPED
- 发送路径:SendDataDirectly 中为 OVERLAPPED.hEvent 创建事件对象,调用 WriteFile;若返回 ERROR_IO_PENDING,则通过 WaitForSingleObject 等待事件;最终通过 GetOverlappedResult 获取实际写入字节数
- 接收路径:ReceiveDataDirectly 中先调用 ClearCommError 查询 cbInQue,再根据需要调用 ReadFile;同样在 ERROR_IO_PENDING 情况下等待 Overlapped.hEvent 并用 GetOverlappedResult 获取结果
- Zmodem 路径:ZmodemSendDataDirectly/ZmodemReceiveDataDirectly 使用独立 OVERLAPPED 对象,发送前可 PurgeComm 清理发送缓冲区,接收时根据 cbInQue 读取
章节来源
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L527-L576)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L306-L401)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L716-L763)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L765-L900)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L382-L420)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L286-L380)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L560-L607)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L609-L744)
### 错误处理与缓冲区清理
- ClearCommError:用于查询通信错误标志与输入缓冲区字节数,避免在无数据时盲目 ReadFile
- PurgeComm:在关闭串口或需要清空缓冲区时使用 PURGE_RXCLEAR/PURGE_TXCLEAR/PURGE_RXABORT/PURGE_TXABORT 清理接收/发送队列
- 日志输出:通过 PrintLogLast 将通信过程与错误信息写入日志文件,便于定位问题
章节来源
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L323-L335)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L268-L284)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L585-L595)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L937-L952)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L267-L284)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L429-L439)
### 事件驱动通信模式
- WaitCommEvent:在接收路径中等待 EV_RXCHAR 等事件触发,避免忙等
- EV_BREAK:断线检测通过 SetCommMask 注册 EV_BREAK,并结合断线检测线程轮询端口是否存在,当端口不可访问时向 UI 发送 WM_BREAKLINE
章节来源
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L189-L196)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L225-L256)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L188-L196)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L224-L255)
- [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328)
### 断线检测线程与 UI 回调
- 断线检测线程:SComPortDetectBreakThreadFun 循环尝试以独占方式打开端口,若返回 ERROR_FILE_NOT_FOUND 则判定断线,调用 CloseComm 并通过 SendMessage(WM_BREAKLINE) 通知 UI
- UI 处理:OnBreakLine 中根据通信 ID 关闭对应串口、更新设备状态、刷新界面
章节来源
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L225-L256)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L224-L255)
- [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328)
### 串口打开、数据收发、异常处理与资源释放的完整调用示例
- 打开串口:OpenComm -> CreateFile -> SetupComm -> Get/SetCommState -> SetCommMask
- 发送数据:SendDataDirectly -> WriteFile(Overlapped) -> WaitForSingleObject -> GetOverlappedResult
- 接收数据:WaitCommEvent(EV_RXCHAR) -> ClearCommError(cbInQue) -> ReadFile(Overlapped) -> WaitForSingleObject -> GetOverlappedResult
- 异常处理:ERROR_IO_PENDING 分支、超时处理、错误日志
- 资源释放:CloseComm -> PurgeComm -> CloseHandle
章节来源
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L126-L223)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L306-L401)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L527-L576)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L268-L284)
### 多线程环境下的线程安全策略
- 断线检测线程:独立线程轮询端口,避免阻塞主线程;线程内对串口句柄的创建/关闭遵循最小化持有原则
- 串口对象成员:构造函数初始化成员变量,析构函数负责关闭句柄与日志;代码中保留了 m_CriticalSection 字段但未启用,建议在多线程并发访问同一实例时增加互斥保护
- UI 与串口交互:通过消息 WM_BREAKLINE 解耦断线检测与 UI 更新,降低锁竞争
章节来源
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L31-L57)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L87-L91)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L30-L57)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L86-L90)
## 依赖关系分析
- SComPort 依赖 Windows 串口 APICreateFile、SetupComm、GetCommState、SetCommState、SetCommMask、WaitCommEvent、ReadFile、WriteFile、GetOverlappedResult、ClearCommError、PurgeComm、CloseHandle
- 常量 READBUFFER_SIZE/WRITEBUFFER_SIZE、COMM_TIMEOUT 来自公共头文件
- UI 通过消息 WM_BREAKLINE 与串口断线检测解耦
```mermaid
graph LR
SComPort["SComPort 类"] --> WinAPI["Windows 串口API"]
SComPort --> Const["常量定义"]
SComPort --> UI["主窗口消息处理"]
WinAPI --> |"CreateFile/SetupComm/SetCommState/SetCommMask"| Port["物理串口"]
WinAPI --> |"WaitCommEvent/ReadFile/WriteFile"| Port
WinAPI --> |"ClearCommError/PurgeComm"| Port
```
图表来源
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L156-L223)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L306-L401)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L527-L576)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L268-L284)
- [Constant.h](file://h/Constant.h#L224-L232)
- [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328)
章节来源
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L156-L223)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L306-L401)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L527-L576)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L268-L284)
- [Constant.h](file://h/Constant.h#L224-L232)
- [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328)
## 性能考量
- 缓冲区大小:READBUFFER_SIZE/WRITEBUFFER_SIZE 为 15360 字节,适合批量传输场景;可根据设备数据速率调整
- 超时控制:COMM_TIMEOUT 为 500ms,用于等待事件或 I/O 完成;在高延迟链路中可适当增大
- 异步 I/OOVERLAPPED + 事件对象避免阻塞,提高吞吐;注意避免多个操作共享同一 OVERLAPPED 导致状态混淆
- 日志输出:频繁写日志会影响性能,建议在调试阶段开启,生产环境关闭或降频
[本节为通用指导,无需列出具体文件来源]
## 故障排查指南
- 打开失败:检查 CreateFile 返回值与 GetLastError;确认端口名称格式(含“\\\\.\”前缀)
- 设置参数失败:检查 SetCommState 返回值;确认 DCB 字段赋值顺序与目标平台兼容性
- 无数据可读:使用 ClearCommError 检查 cbInQue;确保已注册 EV_RXCHAR 事件并正确等待
- 发送卡住:确认 WriteFile 返回 ERROR_IO_PENDING 后正确等待 Overlapped.hEvent;检查线程优先级与事件对象创建
- 断线误报:断线检测线程轮询间隔与 UI 回调需匹配;避免重复创建线程导致资源泄漏
- 日志定位:通过 PrintLogLast 输出的时间戳与错误码快速定位问题
章节来源
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L156-L223)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L323-L335)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L527-L576)
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L937-L952)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L155-L196)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L286-L380)
- [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L560-L607)
## 结论
SComPort 类通过 Windows 异步串口 API 提供了稳定可靠的串口通信能力,覆盖初始化、异步读写、事件驱动接收、断线检测与资源管理。结合 ClearCommError 与 PurgeComm,能够有效处理缓冲区与错误状态;通过 WM_BREAKLINE 与 UI 解耦,提升用户体验。建议在多线程并发访问同一实例时增加互斥保护,并根据实际数据速率调整缓冲区与超时参数。
[本节为总结性内容,无需列出具体文件来源]
## 附录
### 关键流程图:ReceiveDataDirectly
```mermaid
flowchart TD
Start(["进入 ReceiveDataDirectly"]) --> CheckHandle["检查串口句柄是否有效"]
CheckHandle --> |无效| Fail["返回失败"]
CheckHandle --> |有效| ClearErr["ClearCommError 查询错误与InQue"]
ClearErr --> InQueZero{"InQue 是否为0"}
InQueZero --> |是| ReturnFalse["返回 FALSE"]
InQueZero --> |否| CalcNeed["计算需要读取字节数(min(最大读取, InQue))"]
CalcNeed --> Read["ReadFile(Overlapped)"]
Read --> Pending{"返回值是否为 FALSE"}
Pending --> |是 且 错误码为 ERROR_IO_PENDING| Wait["WaitForSingleObject(Overlapped.hEvent)"]
Wait --> Gor["GetOverlappedResult 获取字节数"]
Gor --> Compare["比较实际字节数与期望字节数"]
Compare --> Log["记录日志"]
Log --> ReturnTrue["返回成功"]
Pending --> |否| CheckRead["检查 ReadFile 返回字节数"]
CheckRead --> Log2["记录日志"]
Log2 --> ReturnTrue
```
图表来源
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L306-L401)
### 关键流程图:SendDataDirectly
```mermaid
flowchart TD
Start(["进入 SendDataDirectly"]) --> CheckHandle["检查串口句柄与输入参数"]
CheckHandle --> |无效| Fail["返回失败"]
CheckHandle --> |有效| Prepare["准备 OVERLAPPED.hEvent 与写缓冲区"]
Prepare --> Write["WriteFile(Overlapped)"]
Write --> Pending{"返回值是否为 FALSE"}
Pending --> |是 且 错误码为 ERROR_IO_PENDING| Wait["WaitForSingleObject(Overlapped.hEvent)"]
Wait --> Timeout{"等待结果"}
Timeout --> |超时| Purge["关闭句柄并返回失败"]
Timeout --> |成功| ReturnTrue["返回成功"]
Pending --> |否| ReturnTrue
```
图表来源
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L527-L576)
### 关键流程图:断线检测线程
```mermaid
flowchart TD
Loop["循环检测"] --> TryOpen["尝试以独占方式打开串口"]
TryOpen --> OpenFail{"打开失败?"}
OpenFail --> |是 且 错误码为 文件不存在| Break["标记断线并关闭串口"]
Break --> PostMsg["发送 WM_BREAKLINE 给 UI"]
PostMsg --> Exit["退出线程"]
OpenFail --> |否| CloseHandle["关闭句柄并继续循环"]
CloseHandle --> Sleep["Sleep(短时间)"]
Sleep --> Loop
```
图表来源
- [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L225-L256)
- [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328)
@@ -0,0 +1,250 @@
# 文件传输协议
<cite>
**本文档引用的文件**
- [Zmodem.cpp](file://cpp/Tools/Zmodem.cpp)
- [Zmodem.h](file://h/Zmodem.h)
- [_zmodem.h](file://h/_zmodem.h)
- [FileTransfer.h](file://h/FileTransfer.h)
- [FileTransfer.cpp](file://cpp/Tools/FileTransfer.cpp)
- [Crc32.cpp](file://cpp/Tools/Crc32.cpp)
- [Crc16.cpp](file://cpp/Tools/Crc16.cpp)
- [SComPort.h](file://h/SComPort.h)
</cite>
## 目录
1. [引言](#引言)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构概述](#架构概述)
5. [详细组件分析](#详细组件分析)
6. [依赖分析](#依赖分析)
7. [性能考虑](#性能考虑)
8. [故障排除指南](#故障排除指南)
9. [结论](#结论)
## 引言
本文档全面阐述了基于Zmodem协议的可靠文件传输实现。该协议在Geomative Studio项目中用于设备与主机之间的固件升级、脚本文件和测量数据传输。Zmodem协议通过其强大的错误恢复机制、断点续传功能和高效的二进制传输,确保了在串行通信等不可靠信道上的数据完整性。本分析将深入解析Zmodem类的核心状态机,包括ZRQINIT请求初始化、ZRINIT应答、ZFILE文件信息帧、ZDATA数据帧、ZEOF文件结束和ZFIN传输结束等关键帧类型的交互流程。同时,文档将详细说明CRC-32校验、断点续传(通过ZRPOS携带文件偏移量)和错误恢复(ZCRC校验请求)机制的工作原理,并分析SendSingleFile和ReceiveSingleFile方法中数据分块读写、帧头封装(SendBinaryHeader)和数据帧发送(SendDataFrame)的实现细节。
## 项目结构
Geomative Studio项目是一个用于地质测量设备管理的复杂软件系统。文件传输功能是其核心模块之一,主要由`cpp/Tools/`目录下的工具类实现。与文件传输直接相关的文件包括`Zmodem.cpp``Zmodem.h`,它们实现了Zmodem协议的核心逻辑。`Zmodem`类继承自`FileTransfer`基类,后者定义了文件传输的通用接口。为了支持Zmodem协议,项目还包含了`Crc16.cpp``Crc32.cpp`文件,用于计算数据校验和。通信底层由`SComPort`类处理,它提供了与串口设备通信的接口。整个文件传输模块通过`FileTransfer`模块进行集成,为上层应用提供统一的文件收发功能。
```mermaid
graph TB
subgraph "文件传输模块"
Zmodem[Zmodem.cpp<br/>Zmodem协议实现]
FileTransfer[FileTransfer.cpp<br/>传输基类]
Crc32[Crc32.cpp<br/>CRC-32校验]
Crc16[Crc16.cpp<br/>CRC-16校验]
end
subgraph "通信模块"
SComPort[SComPort.h<br/>串口通信]
end
subgraph "应用层"
FileTransferModule[FileTransfer模块<br/>文件传输应用]
end
Zmodem --> FileTransfer
Zmodem --> Crc32
Zmodem --> Crc16
Zmodem --> SComPort
FileTransferModule --> Zmodem
```
**Diagram sources**
- [Zmodem.cpp](file://cpp/Tools/Zmodem.cpp#L1-L1940)
- [FileTransfer.cpp](file://cpp/Tools/FileTransfer.cpp#L1-L47)
- [Crc32.cpp](file://cpp/Tools/Crc32.cpp#L1-L51)
- [Crc16.cpp](file://cpp/Tools/Crc16.cpp#L1-L54)
- [SComPort.h](file://h/SComPort.h#L1-L74)
**Section sources**
- [Zmodem.cpp](file://cpp/Tools/Zmodem.cpp#L1-L1940)
- [Zmodem.h](file://h/Zmodem.h#L1-L143)
- [FileTransfer.h](file://h/FileTransfer.h#L1-L49)
## 核心组件
Zmodem协议的核心组件是`Zmodem`类,它封装了协议的所有状态机和数据处理逻辑。该类通过继承`FileTransfer`基类,实现了`Send``Receive`两个核心接口。`Zmodem`类维护了多个关键状态变量,如`transmitted_file_position`(已发送的文件位置)、`received_file_position`(已接收的文件位置)和`receiver_wants_crc32`(接收方是否支持CRC-32),这些变量共同驱动着协议的状态转换。协议的可靠性主要依赖于`SendBinaryHeader``ReadHeader``SendDataFrame`等方法,它们负责数据的封装、解析和校验。此外,`GetRinitHeader``SyncWithReceiver`等方法处理了协议的初始化和同步过程,确保了通信双方的协调一致。
**Section sources**
- [Zmodem.cpp](file://cpp/Tools/Zmodem.cpp#L33-L1940)
- [Zmodem.h](file://h/Zmodem.h#L31-L140)
## 架构概述
Zmodem协议的架构是一个典型的请求-响应式状态机,其核心是围绕一系列预定义的帧类型(Frame Types)进行交互。整个传输过程始于发送方的`ZRQINIT`请求,接收方通过`ZRINIT`帧进行应答,完成初始化握手。随后,发送方通过`ZFILE`帧发送文件名和元数据,接收方确认后,数据传输进入主循环,由`ZDATA`数据帧和`ZACK`确认帧构成。当文件传输完毕,发送方发送`ZEOF`帧,接收方确认后,发送方再发送`ZFIN`帧结束整个会话。该架构通过`ZRPOS`帧实现了断点续传,通过`ZCRC`帧实现了错误恢复,并通过`CAN`序列提供了紧急中断机制。
```mermaid
sequenceDiagram
participant 发送方 as 发送方 (Zmodem)
participant 接收方 as 接收方 (Zmodem)
发送方->>接收方 : ZRQINIT (请求初始化)
接收方->>发送方 : ZRINIT (初始化应答)
发送方->>接收方 : ZFILE (文件信息)
接收方->>发送方 : ZRPOS (请求从指定位置开始)
loop 数据传输
发送方->>接收方 : ZDATA (数据帧)
接收方->>发送方 : ZACK (确认)
end
发送方->>接收方 : ZEOF (文件结束)
接收方->>发送方 : ZRPOS (确认)
发送方->>接收方 : ZFIN (会话结束)
接收方->>发送方 : 'OO' (确认)
```
**Diagram sources**
- [Zmodem.cpp](file://cpp/Tools/Zmodem.cpp#L87-L126)
- [Zmodem.cpp](file://cpp/Tools/Zmodem.cpp#L385-L451)
- [_zmodem.h](file://h/_zmodem.h#L15-L36)
## 详细组件分析
### Zmodem状态机分析
Zmodem协议的状态机是其可靠性的核心。状态机的运行由`Send``Receive`方法驱动,它们分别调用`SendSingleFile``ReceiveSingleFile`来处理单个文件的传输。状态转换主要通过`ReadHeader`方法接收的帧类型来触发。例如,在`SendSingleFile`方法中,一个`ZCRC`帧会触发发送方重新计算并发送文件的CRC校验值;一个`ZRPOS`帧则会触发发送方跳转到指定的文件偏移量继续发送。在`ReceiveSingleFile`方法中,状态机通过`SendHexHeader`主动发送`ZRPOS`帧来请求数据,根据接收到的`ZDATA``ZEOF``ZSKIP`等帧来决定后续动作。这种基于事件驱动的状态机设计,使得协议能够灵活应对各种网络状况。
```mermaid
stateDiagram-v2
[*] --> 初始化
初始化 --> 发送文件信息 : ZFILE
发送文件信息 --> 等待响应 : 发送ZFILE
等待响应 --> 数据传输 : ZRPOS
等待响应 --> 跳过文件 : ZSKIP
等待响应 --> 错误恢复 : ZCRC
数据传输 --> 发送数据帧 : ZDATA
发送数据帧 --> 等待确认 : 发送ZDATA
等待确认 --> 数据传输 : ZACK
等待确认 --> 错误恢复 : TIMEOUT
错误恢复 --> 发送数据帧 : 重发
发送数据帧 --> 文件结束 : ZEOF
文件结束 --> 等待结束 : 发送ZEOF
等待结束 --> 传输结束 : ZRPOS
传输结束 --> [*] : ZFIN
```
**Diagram sources**
- [Zmodem.cpp](file://cpp/Tools/Zmodem.cpp#L153-L258)
- [Zmodem.cpp](file://cpp/Tools/Zmodem.cpp#L558-L775)
**Section sources**
- [Zmodem.cpp](file://cpp/Tools/Zmodem.cpp#L153-L775)
### 校验与错误恢复机制分析
Zmodem协议通过CRC校验和超时重试机制来保证数据的完整性。协议支持CRC-16和CRC-32两种校验算法,由`receiver_wants_crc32`标志位决定。`Crc32``Crc16`类实现了校验算法,其中`Crc32`类使用了预计算的查找表来提高计算效率。当接收方检测到数据帧的CRC校验失败时,它会发送一个`ZNAK`(否定应答)或`ZCRC`(请求校验)帧。发送方在`SyncWithReceiver`方法中处理这些错误,根据情况重发数据或重新同步。`ReadDataFrame`方法是错误处理的关键,它会循环读取数据直到收到正确的`GOTCRCW`等结束标志,否则会返回`ZERROR`。此外,协议还定义了`TIMEOUT``GARBAGE_COUNT`等错误码,用于处理超时和垃圾数据。
```mermaid
flowchart TD
A[开始读取数据帧] --> B{是否为ZDLE?}
B --> |是| C[读取转义字符]
B --> |否| D{是否为有效数据?}
D --> |是| E[更新CRC校验]
D --> |否| F[处理XON/XOFF]
C --> G{转义字符类型}
G --> H[ZCRCE/G/Q/W] --> I[读取4字节CRC]
G --> J[ZCAN] --> K[返回ZCAN]
G --> L[ZRUB0/1] --> M[替换为0x7f/0xff]
I --> N{CRC校验是否通过?}
N --> |是| O[返回GOTCRC*]
N --> |否| P[返回ZERROR]
E --> Q{缓冲区满?}
Q --> |否| B
Q --> |是| R[返回ZERROR]
```
**Diagram sources**
- [Zmodem.cpp](file://cpp/Tools/Zmodem.cpp#L1154-L1340)
- [Crc32.cpp](file://cpp/Tools/Crc32.cpp#L1-L51)
- [Crc16.cpp](file://cpp/Tools/Crc16.cpp#L1-L54)
**Section sources**
- [Zmodem.cpp](file://cpp/Tools/Zmodem.cpp#L1154-L1340)
- [Crc32.cpp](file://cpp/Tools/Crc32.cpp#L1-L51)
- [Crc16.cpp](file://cpp/Tools/Crc16.cpp#L1-L54)
### 数据传输流程分析
文件传输的核心流程由`SendSingleFile``ReceiveSingleFile`方法实现。发送流程始于`Send`方法,它首先发送`ZRQINIT`帧,然后进入`SendSingleFile`。在`SendSingleFile`中,发送方构造包含文件名和长度的`ZFILE`帧并发送,随后等待接收方的`ZRPOS`指令。一旦收到`ZRPOS`,发送方调用`SendFileContents`方法,该方法循环读取文件数据,封装成`ZDATA`帧发送,并根据策略(如`ZCRCW`)等待接收方的`ZACK`确认。接收流程始于`Receive`方法,它调用`WakeUpSender`发送`ZRINIT`帧,然后进入`ReceiveSingleFile`。接收方通过循环发送`ZRPOS`帧来请求数据,`ReadDataFrame`方法负责接收和校验`ZDATA`帧,并将有效数据写入文件。整个流程通过`m_WriteBuff``buff`等缓冲区来优化I/O性能。
**Section sources**
- [Zmodem.cpp](file://cpp/Tools/Zmodem.cpp#L153-L378)
- [Zmodem.cpp](file://cpp/Tools/Zmodem.cpp#L558-L775)
## 依赖分析
Zmodem协议的实现依赖于多个层次的组件。最底层是`SComPort`类,它通过`ZmodemSendDataDirectly``ZmodemReceiveDataDirectly`方法提供串口的直接读写能力。`Zmodem`类依赖于`Crc32``Crc16`类来计算数据校验和。`Zmodem`类本身是`FileTransfer`抽象基类的具体实现,它必须实现`Send``Receive`两个纯虚函数。此外,`Zmodem`类还依赖于标准C库的文件操作函数(如`fopen`, `fread`, `fclose`)来处理本地文件。这些依赖关系清晰地划分了职责,使得协议逻辑与底层通信、文件系统和校验算法解耦。
```mermaid
classDiagram
class FileTransfer {
<<abstract>>
+Send(files[] char*) BOOL
+Receive(Path CString) BOOL
-error(fmt char*, ...)
-status(fmt char*, ...)
}
class Zmodem {
-port CSComPort*
-file FILE*
-file_length long
-byte_count long
-receiver_wants_crc32 int
-received_file_position long
+Send(files[] char*) BOOL
+Receive(Path CString) BOOL
-SendSingleFile(name char*) int
-ReceiveSingleFile(Path CString) int
-SendBinaryHeader(length int, type int, header char*) void
-ReadHeader(header char*) int
-SendDataFrame(buffer char*, length int, frameend int) void
}
class Crc32 {
-crc unsigned long
+Crc32(init_value unsigned long)
+update(c int) void
+value() unsigned long
}
class Crc16 {
-crc unsigned short
+Crc16(init_value unsigned short)
+update(c int) void
+value() unsigned short
}
class CSComPort {
+ZmodemSendDataDirectly(pDataBuff char*, iDataSize int) BOOL
+ZmodemReceiveDataDirectly(pDataBuff char*, iDataSize int*) BOOL
}
FileTransfer <|-- Zmodem : "继承"
Zmodem --> Crc32 : "使用"
Zmodem --> Crc16 : "使用"
Zmodem --> CSComPort : "使用"
```
**Diagram sources**
- [FileTransfer.h](file://h/FileTransfer.h#L26-L46)
- [Zmodem.h](file://h/Zmodem.h#L31-L140)
- [Crc32.h](file://h/Crc32.h#L12-L28)
- [Crc16.h](file://h/Crc16.h#L12-L27)
- [SComPort.h](file://h/SComPort.h#L14-L73)
**Section sources**
- [FileTransfer.h](file://h/FileTransfer.h#L26-L46)
- [Zmodem.h](file://h/Zmodem.h#L31-L140)
- [Crc32.h](file://h/Crc32.h#L12-L28)
- [Crc16.h](file://h/Crc16.h#L12-L27)
- [SComPort.h](file://h/SComPort.h#L14-L73)
## 性能考虑
Zmodem协议的性能主要体现在其高效的二进制传输和流控机制上。协议通过`ZBIN32``ZBIN`帧头支持二进制传输,避免了ASCII编码的开销。`SendDataFrame`方法在发送数据时,会根据`receiver_wants_crc32`标志选择使用CRC-32或CRC-16,CRC-32虽然计算开销稍大,但能提供更强的错误检测能力。为了优化性能,代码中使用了`m_WriteBuff`写缓冲区,将多个`SendChar`调用合并为一次`ZmodemSendDataDirectly`调用,减少了系统调用的次数。在接收端,`ReadBuff`方法也实现了读缓冲,提高了数据读取效率。此外,`receiver_buffer_length`参数允许接收方告知发送方其缓冲区大小,从而实现简单的流量控制,防止接收方缓冲区溢出。
## 故障排除指南
当文件传输失败时,应首先检查日志文件(如`log\\zmodemSZLog.txt``log\\zmodemRZLog.txt`)。常见的错误包括:
- **ZERROR**: 通用错误,可能由多种原因引起,需结合上下文日志分析。
- **TIMEOUT**: 通信超时,检查串口连接、波特率设置和设备电源。
- **ZCAN**: 通信被对方取消,检查是否有用户手动中断或设备异常。
- **Bad CRC**: 数据校验失败,通常由通信线路噪声或波特率不匹配引起。
- **Open file failed**: 无法创建或打开文件,检查目标路径权限和磁盘空间。
代码中通过`error``status`方法输出详细的调试信息,并通过`PrintLogLast`方法将时间戳信息写入日志,便于问题定位。对于`ZCRC`错误,协议会自动请求重传,但如果错误频繁发生,则应检查物理连接。
**Section sources**
- [Zmodem.cpp](file://cpp/Tools/Zmodem.cpp#L28-L47)
- [Zmodem.cpp](file://cpp/Tools/Zmodem.cpp#L1906-L1921)
- [FileTransfer.cpp](file://cpp/Tools/FileTransfer.cpp#L28-L46)
## 结论
本文档详细分析了Geomative Studio项目中基于Zmodem协议的文件传输实现。该实现是一个功能完整、健壮可靠的协议栈,它通过精心设计的状态机、强大的CRC校验和灵活的断点续传机制,确保了在复杂工业环境下的数据传输成功率。`Zmodem`类的代码结构清晰,职责分明,通过继承和组合的方式,有效地管理了与底层通信、文件系统和校验算法的依赖关系。该模块为设备的固件升级、脚本部署和数据回传提供了坚实的基础,是Geomative Studio系统不可或缺的核心组件。
@@ -0,0 +1,326 @@
# 网络通信
<cite>
**本文引用的文件**
- [TcpClient.h](file://h/TcpClient.h)
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp)
- [TransferCtrl.h](file://h/TransferCtrl.h)
- [TransferCtrl.cpp](file://cpp/Tools/TransferCtrl.cpp)
- [CtrlProtocolDef.h](file://h/CtrlProtocolDef.h)
- [Constant.h](file://h/Constant.h)
- [devmngframe.cpp](file://cpp/Views/devmngframe.cpp)
- [checkupdate.cpp](file://cpp/Tools/checkupdate.cpp)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件围绕 TcpClient 类实现的 TCP 客户端通信机制进行深入解析,覆盖 socket 创建、SOCK_STREAM 流式套接字配置、SO_LINGER 和 SO_KEEPALIVE 等套接字选项的作用与效果;connect 建立连接的三次握手过程与超时策略;SendData/RecvData 中 send/recv 的调用逻辑、非阻塞模式下的 select 使用、MSG_PEEK 探测接收状态与错误码处理;ReConnect 重连机制的实现、closesocket 资源释放与新 socket 重建的必要性;GetConnectStatus 通过 select+recv(MSG_PEEK) 组合检测连接存活状态的技术细节。最后给出连接管理、数据收发、异常重连与资源清理的完整流程图与示例路径,并说明在设备固件升级与数据同步场景中的应用。
## 项目结构
该项目为桌面应用工程,网络通信相关代码集中在 Tools 子目录,核心类为 TcpClient 与派生类 CTransferCtrl;协议定义位于 CtrlProtocolDef.h;常量与全局配置位于 Constant.h;设备管理与同步界面位于 Views 子目录;固件升级逻辑位于 Tools 子目录。
```mermaid
graph TB
subgraph "网络工具层"
TC["TcpClient<br/>h/TcpClient.h<br/>cpp/Tools/TcpClient.cpp"]
TCtrl["CTransferCtrl<br/>h/TransferCtrl.h<br/>cpp/Tools/TransferCtrl.cpp"]
end
subgraph "协议与常量"
Proto["CtrlProtocolDef.h"]
Const["Constant.h"]
end
subgraph "业务视图与操作"
View["devmngframe.cpp"]
Upg["checkupdate.cpp"]
end
TCtrl --> TC
TCtrl --> Proto
TC --> Const
View --> TCtrl
Upg --> TCtrl
```
图表来源
- [TcpClient.h](file://h/TcpClient.h#L1-L76)
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L1-L449)
- [TransferCtrl.h](file://h/TransferCtrl.h#L1-L50)
- [TransferCtrl.cpp](file://cpp/Tools/TransferCtrl.cpp#L1-L200)
- [CtrlProtocolDef.h](file://h/CtrlProtocolDef.h#L1-L120)
- [Constant.h](file://h/Constant.h#L220-L235)
- [devmngframe.cpp](file://cpp/Views/devmngframe.cpp#L470-L504)
- [checkupdate.cpp](file://cpp/Tools/checkupdate.cpp#L1-L120)
章节来源
- [TcpClient.h](file://h/TcpClient.h#L1-L76)
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L1-L120)
- [TransferCtrl.h](file://h/TransferCtrl.h#L1-L50)
- [TransferCtrl.cpp](file://cpp/Tools/TransferCtrl.cpp#L1-L120)
- [CtrlProtocolDef.h](file://h/CtrlProtocolDef.h#L1-L120)
- [Constant.h](file://h/Constant.h#L220-L235)
## 核心组件
- TcpClient:封装 TCP 套接字生命周期、连接管理、数据收发、连接状态检测与重连。
- CTransferCtrl:基于 TcpClient 的控制协议封装,负责控制命令的打包、发送与响应解析,以及与业务层交互。
章节来源
- [TcpClient.h](file://h/TcpClient.h#L18-L76)
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L26-L120)
- [TransferCtrl.h](file://h/TransferCtrl.h#L34-L50)
- [TransferCtrl.cpp](file://cpp/Tools/TransferCtrl.cpp#L120-L180)
## 架构总览
下图展示 TcpClient 与 CTransferCtrl 的类关系、关键成员与方法,以及与协议定义的关联。
```mermaid
classDiagram
class CTcpClient {
+CTcpClient()
+~CTcpClient()
+Initialize() bool
+ConnectToServer(strIP, wPort) bool
+ReConnect() bool
+SendData(pData, iLen) int
+RecvData(pData, iLen, iActualLen) bool
+GetConnectStatus() bool
+CloseConnect() bool
+Disconnect() bool
+ClearRecvBuffer() bool
-InitailTcp() bool
-ConnectServer(strAddr, wPort) bool
-m_strIP : CString
-m_wPort : WORD
-m_iSndBuf : int
-m_iRcvBuf : int
-m_sockClient : SOCKET
-m_bIsConnect : bool
-m_bIsInitialed : bool
-m_pClearRcvBuf : char*
}
class CTransferCtrl {
+Initialize() bool
+SendCtrlInfo(pCtrlHeader, pData, wDataLen, DataType) bool
+RecvCtrlMsg(pData, pLen, iMaxLen, iTimeout) int
+RecvRspMsg(pData, pLen, iMaxLen, iTimeout) int
+GetCtrlRspLength(ucCMd) int
+InitialClient() bool
+IsCtrlMsg(ucCMd) bool
+IsPlcStatusMsg(ucCmd, ucSrcType) bool
+GetPlcAddr() uint32
+GetCurrCtrlCmd() int
-RecvEnoughMsg(pData, iLen, iTimeOut) bool
-RecvCommRspMsg(pData, pLen, iMaxLen, iRcvTimeout, bIsRcvCtrl) int
-SendCtrlInfoToDev(pTransInfo, pData, wDataLen, ucDataType) bool
}
CTransferCtrl --|> CTcpClient : "继承"
```
图表来源
- [TcpClient.h](file://h/TcpClient.h#L18-L76)
- [TransferCtrl.h](file://h/TransferCtrl.h#L34-L50)
## 详细组件分析
### 套接字创建与流式配置
- socket 创建:构造函数初始化成员,InitailTcp 在首次使用前创建流式套接字(AF_INET, SOCK_STREAM)。
- 缓冲区设置:通过 SO_RCVBUF/SO_SNDBUF 设置收发缓冲区大小。
- 连接保活:启用 SO_KEEPALIVE,提升链路异常检测能力。
- 关闭策略:禁用 SO_LINGER(立即丢弃未发送数据),避免长时间等待导致阻塞。
章节来源
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L48-L99)
### connect 建立连接与三次握手
- 连接发起:ConnectServer 将目标地址转换为 sockaddr_in 并调用 connect。
- 三次握手:由系统内核完成,应用层无需显式处理握手细节。
- 超时策略:当前实现未对 connect 设置超时,如需超时可在上层封装 select 或非阻塞 + 超时判断(建议在业务层统一处理)。
章节来源
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L101-L130)
### SendData 发送逻辑
- 连接状态检查:发送前调用 GetConnectStatus,若连接中断则返回错误。
- send 调用:调用 send 发送数据,记录日志与错误码。
- 返回值语义:返回实际发送字节数或错误标识,便于上层判断。
章节来源
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L149-L177)
### RecvData 接收逻辑与非阻塞 select
- 非阻塞探测:使用 select 设置超时(微秒级),在无数据时快速返回。
- 可读判定:select 返回正值且 FD_ISSET 检测到可读时,执行 recv。
- 异常处理:recv 返回非正数时标记连接中断并更新状态。
章节来源
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L282-L354)
### GetConnectStatus 连接存活检测
- select 立即轮询:使用零超时的 select 检查可读事件。
- MSG_PEEK 探测:对可读套接字执行 MSG_PEEK 探测,若返回值小于等于 0,通常表示对端关闭或连接异常。
- 状态维护:根据探测结果更新 m_bIsConnect。
章节来源
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L201-L249)
### ReConnect 重连机制
- 资源释放:先调用 CloseConnect 关闭当前连接。
- 新建套接字:调用 InitailTcp 重新创建套接字,以规避某些平台/驱动下“仅关闭连接无法再次成功”的限制。
- 再连接:使用原 IP/端口重新 ConnectToServer。
章节来源
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L252-L278)
### CTransferCtrl 控制协议封装
- 初始化:Initialize 调用基类 InitailTcp 完成套接字准备。
- 发送控制:SendCtrlInfoToDev 构造协议头与数据,调用 SendData 发送。
- 接收控制:RecvCtrlMsg/RecvRspMsg/RecvCommRspMsg 实现头部解析、长度校验、CRC 校验与超时处理。
- 协议常量:使用 CtrlProtocolDef.h 中的命令字、结构体与枚举。
章节来源
- [TransferCtrl.cpp](file://cpp/Tools/TransferCtrl.cpp#L72-L180)
- [TransferCtrl.cpp](file://cpp/Tools/TransferCtrl.cpp#L245-L400)
- [CtrlProtocolDef.h](file://h/CtrlProtocolDef.h#L441-L548)
### 设备固件升级与数据同步场景
- 固件升级:checkupdate.cpp 展示了通过网络下载升级文件、向设备发送文件与执行升级命令的流程,体现网络通信在升级中的作用。
- 数据同步:devmngframe.cpp 展示了设备参数同步、任务参数下发与云端托管任务的交互,CTransferCtrl 在其中承担控制通道。
章节来源
- [checkupdate.cpp](file://cpp/Tools/checkupdate.cpp#L1-L120)
- [checkupdate.cpp](file://cpp/Tools/checkupdate.cpp#L1042-L1336)
- [devmngframe.cpp](file://cpp/Views/devmngframe.cpp#L470-L504)
## 依赖关系分析
- CTransferCtrl 依赖 TcpClient 提供的套接字生命周期与收发能力。
- CTransferCtrl 依赖 CtrlProtocolDef.h 的协议结构与命令字。
- TcpClient 依赖 Constant.h 中的缓冲区大小与超时常量。
- 视图层与操作层通过 CTransferCtrl 间接使用网络通信。
```mermaid
graph LR
TCtrl["CTransferCtrl"] --> TC["CTcpClient"]
TCtrl --> Proto["CtrlProtocolDef.h"]
TC --> Const["Constant.h"]
View["devmngframe.cpp"] --> TCtrl
Upg["checkupdate.cpp"] --> TCtrl
```
图表来源
- [TransferCtrl.cpp](file://cpp/Tools/TransferCtrl.cpp#L1-L120)
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L1-L120)
- [CtrlProtocolDef.h](file://h/CtrlProtocolDef.h#L1-L120)
- [Constant.h](file://h/Constant.h#L220-L235)
- [devmngframe.cpp](file://cpp/Views/devmngframe.cpp#L470-L504)
- [checkupdate.cpp](file://cpp/Tools/checkupdate.cpp#L1-L120)
## 性能考量
- 缓冲区大小:通过 SO_RCVBUF/SO_SNDBUF 调整可减少系统调用次数,提高吞吐。
- 非阻塞接收:使用 select 微秒级超时,避免长时间阻塞,适合高频轮询场景。
- MSG_PEEK 探测:零拷贝探测连接状态,降低开销。
- 重连策略:在出现异常时主动重建套接字,避免复用旧句柄导致的不可达。
[本节为通用指导,无需列出具体文件来源]
## 故障排查指南
- 连接失败:检查 InitailTcp 是否成功创建套接字、SOCK_STREAM 参数是否正确、SO_LINGER 与 SO_KEEPALIVE 设置是否生效。
- 发送失败:关注 WSAGetLastError 返回值,结合日志定位错误码。
- 接收超时:确认 RecvData 的 select 超时设置是否合理,避免过短导致频繁超时。
- 连接中断:GetConnectStatus 的 MSG_PEEK 探测返回非正数时,应视为连接异常并触发重连。
- 重连无效:若仅关闭连接而不重建套接字,可能无法再次连接,需遵循 ReConnect 的重建策略。
章节来源
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L48-L99)
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L149-L177)
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L282-L354)
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L201-L249)
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L252-L278)
## 结论
TcpClient 提供了简洁可靠的 TCP 客户端基础能力:套接字创建与配置、连接管理、非阻塞数据收发、连接状态探测与重连。CTransferCtrl 在此基础上实现了控制协议的编解码与业务交互,支撑设备固件升级与数据同步等场景。通过 MSG_PEEK 与 select 的组合,能够高效检测连接状态并在异常时快速恢复。建议在业务层补充 connect 超时与更细粒度的错误分类,以进一步提升鲁棒性。
[本节为总结性内容,无需列出具体文件来源]
## 附录
### 关键流程图
#### 连接建立与状态检测序列
```mermaid
sequenceDiagram
participant App as "应用"
participant Client as "CTcpClient"
participant Kernel as "系统内核"
App->>Client : "Initialize()"
Client->>Kernel : "socket(AF_INET, SOCK_STREAM)"
Client-->>App : "返回初始化结果"
App->>Client : "ConnectToServer(IP, Port)"
Client->>Kernel : "connect(目标地址)"
Kernel-->>Client : "三次握手完成/失败"
Client-->>App : "返回连接结果"
App->>Client : "GetConnectStatus()"
Client->>Kernel : "select(零超时)+recv(MSG_PEEK)"
Kernel-->>Client : "返回可读/不可读"
Client-->>App : "返回连接状态"
```
图表来源
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L48-L130)
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L201-L249)
#### 数据发送与接收流程
```mermaid
flowchart TD
Start(["进入 RecvData"]) --> CheckConn["检查连接状态"]
CheckConn --> ConnOK{"连接正常?"}
ConnOK --> |否| RetFalse["返回失败"]
ConnOK --> |是| SelectWait["select 设置微秒级超时"]
SelectWait --> SelRet{"select 返回值"}
SelRet --> |错误| HandleErr["记录错误并返回失败"]
SelRet --> |超时(0)| RetZero["返回成功(0字节)"]
SelRet --> |可读(>0)| FDCheck["FD_ISSET 检测套接字"]
FDCheck --> FDOK{"可读信号有效?"}
FDOK --> |否| HandleErr
FDOK --> |是| Recv["recv 接收数据"]
Recv --> RecvRet{"recv 返回值<=0"}
RecvRet --> |是| MarkDown["标记连接中断并返回失败"]
RecvRet --> |否| SaveLen["保存实际接收长度"]
SaveLen --> Log["记录日志"]
Log --> Done(["返回成功"])
```
图表来源
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L282-L354)
#### 重连机制流程
```mermaid
flowchart TD
RCStart(["ReConnect 开始"]) --> CloseOld["CloseConnect 关闭旧连接"]
CloseOld --> NewSock["InitailTcp 重新创建套接字"]
NewSock --> NewSockOK{"创建成功?"}
NewSockOK --> |否| ShowErr["提示套接字初始化失败"]
ShowErr --> RCEnd(["结束"])
NewSockOK --> |是| Reconnect["ConnectToServer(IP, Port)"]
Reconnect --> RCResult{"连接成功?"}
RCResult --> |是| RCOK["返回成功"]
RCResult --> |否| RCFail["返回失败"]
RCOK --> RCEnd
RCFail --> RCEnd
```
图表来源
- [TcpClient.cpp](file://cpp/Tools/TcpClient.cpp#L252-L278)
### 应用示例路径
- 设备参数同步与云端托管任务交互:参见 [devmngframe.cpp](file://cpp/Views/devmngframe.cpp#L470-L504)
- 固件升级流程(下载、发送文件、执行升级命令):参见 [checkupdate.cpp](file://cpp/Tools/checkupdate.cpp#L1042-L1336)
- 控制协议发送与接收:参见 [TransferCtrl.cpp](file://cpp/Tools/TransferCtrl.cpp#L120-L180)、[TransferCtrl.cpp](file://cpp/Tools/TransferCtrl.cpp#L245-L400)
@@ -0,0 +1,425 @@
# 通信工具
<cite>
**本文档引用的文件**
- [SComPort.cpp](file://cpp\Tools\SComPort.cpp)
- [SComPort.h](file://h\SComPort.h)
- [TcpClient.cpp](file://cpp\Tools\TcpClient.cpp)
- [TcpClient.h](file://h\TcpClient.h)
- [Zmodem.cpp](file://cpp\Tools\Zmodem.cpp)
- [Zmodem.h](file://h\Zmodem.h)
- [FileTransfer.cpp](file://cpp\Tools\FileTransfer.cpp)
- [FileTransfer.h](file://h\FileTransfer.h)
- [FileTransfer_crul.cpp](file://cpp\Tools\FileTransfer_crul.cpp)
- [FileTransfer_crul.h](file://h\FileTransfer_crul.h)
- [HttpDownload.cpp](file://cpp\Tools\HttpDownload.cpp)
- [HttpDownload.h](file://h\HttpDownload.h)
- [Constant.h](file://h\Constant.h)
- [CtrlProtocolDef.h](file://h\CtrlProtocolDef.h)
</cite>
## 目录
1. [引言](#引言)
2. [串口通信模块](#串口通信模块)
3. [TCP通信模块](#tcp通信模块)
4. [Zmodem文件传输协议](#zmodem文件传输协议)
5. [文件传输协作机制](#文件传输协作机制)
6. [HTTP下载功能](#http下载功能)
7. [应用场景与调用示例](#应用场景与调用示例)
8. [最佳实践](#最佳实践)
9. [结论](#结论)
## 引言
GeomativeStudio中的通信工具集成了多种通信方式,包括串口通信、TCP通信、Zmodem协议文件传输和HTTP下载功能。这些工具为设备连接、固件升级和数据同步等关键功能提供了底层支持。本文档详细分析了这些通信模块的实现机制与使用方法。
## 串口通信模块
### SComPort类实现
`SComPort`类封装了Windows平台下的串口通信功能,提供了异步读写、超时控制和多线程安全处理等特性。
**串口初始化与配置**
`SComPort`类通过`OpenComm`方法初始化串口连接,设置通信参数如波特率(115200)、数据位(8位)、奇偶校验(无)和停止位(1位)。该类使用重叠I/O(Overlapped I/O)实现异步通信,确保主线程不会被阻塞。
```mermaid
classDiagram
class CSComPort {
+CString m_szComName
+HANDLE m_hCom
+HWND m_hOwnerWnd
+long m_lCommID
+DCB m_dcbBlock
+char m_aWriteBuffer[WRITEBUFFER_SIZE]
+int m_iWriteSize
+char m_aReadBuffer[READBUFFER_SIZE]
+int m_iReadSize
+DWORD m_dwBLThreadID
+HANDLE m_hBLThread
+BOOL m_bIsDetBreakFun
+BOOL m_bIsScanBreak
+DWORD m_dwFactdata
+FILE* m_Log
+CSComPort()
+CSComPort(HWND hOwnerWnd, long lCommID)
+~CSComPort()
+BOOL FindComName(CStringArray* pStringArray)
+BOOL OpenComm(CString szComName)
+void SetOwnerWnd(HWND hOwnerWnd)
+void SetCommID(long lCommID)
+void CloseComm()
+BOOL ReceiveDataDirectly(char* pDataBuff, int* iDataSize)
+BOOL SendDataDirectly(char* pDataBuff, int iDataSize)
+void SetScanBreakSign(BOOL bIsScanBreak)
+void ClearCommSendBuff()
+void ClearCommReceiveBuff()
+BOOL ZmodemReceiveDataDirectly(char* pDataBuff, int* iDataSize)
+BOOL ZmodemSendDataDirectly(char* pDataBuff, int iDataSize)
+static UINT SComPortDetectBreakThreadFun(LPVOID pParam)
}
```
**Diagram sources**
- [SComPort.h](file://h\SComPort.h#L14-L73)
- [SComPort.cpp](file://cpp\Tools\SComPort.cpp#L31-L285)
**异步读写机制**
`ReceiveDataDirectly``SendDataDirectly`方法实现了异步读写功能。读取操作首先通过`ClearCommError`检查输入缓冲区中的数据量,然后使用`ReadFile`函数进行异步读取。如果`ReadFile`立即返回`ERROR_IO_PENDING`,则通过`GetOverlappedResult`等待操作完成。
**超时控制**
通信超时通过`GetOverlappedResult`的最后一个参数`TRUE`实现,该参数指示函数应阻塞直到操作完成或超时。日志记录功能通过`fopen`打开日志文件"LOG\\commLog.txt"实现,所有发送和接收的数据都会被记录。
**多线程安全处理**
`SComPort`类通过`m_hBLThread`线程监控串口连接状态。`SComPortDetectBreakThreadFun`线程函数定期尝试重新打开串口,如果失败则通过`SendMessage`通知所有者窗口串口已断开。
**Section sources**
- [SComPort.cpp](file://cpp\Tools\SComPort.cpp#L306-L403)
- [SComPort.cpp](file://cpp\Tools\SComPort.cpp#L527-L576)
- [SComPort.cpp](file://cpp\Tools\SComPort.cpp#L225-L256)
## TCP通信模块
### TcpClient类实现
`CTcpClient`类实现了TCP客户端通信功能,支持连接管理、数据收发和异常重连。
```mermaid
classDiagram
class CTcpClient {
+CString m_strIP
+WORD m_wPort
+SOCKET m_sockClient
+int m_iRcvBuf
+int m_iSndBuf
+bool m_bIsConnect
+char* m_pClearRcvBuf
+CTcpClient()
+~CTcpClient()
+bool InitailTcp()
+bool ConnectServer(CString strAddr, WORD wPort)
+bool ConnectToServer(CString strIP, WORD wPort)
+int SendData(const char* pData, int wLen)
+bool CloseConnect()
+bool GetConnectStatus()
+bool ReConnect()
+bool RecvData(char* pData, int iLen, int& iActualLen)
+bool Disconnect()
+bool ClearRecvBuffer()
}
```
**Diagram sources**
- [TcpClient.h](file://h\TcpClient.h#L18-L75)
- [TcpClient.cpp](file://cpp\Tools\TcpClient.cpp#L26-L39)
**连接管理**
`InitailTcp`方法初始化套接字,设置套接字选项如`SO_LINGER`、接收/发送缓冲区大小和`SO_KEEPALIVE``ConnectServer`方法建立与服务器的连接,`CloseConnect`方法关闭连接并释放资源。
**数据收发**
`SendData`方法使用`send`函数发送数据,`RecvData`方法使用`select`函数实现非阻塞接收。`select`函数检查套接字是否有数据可读,超时时间为25毫秒。
**异常重连**
`ReConnect`方法实现了异常重连机制。当连接断开时,先调用`CloseConnect`关闭现有连接,然后重新初始化套接字并通过`ConnectToServer`重新连接。
**Section sources**
- [TcpClient.cpp](file://cpp\Tools\TcpClient.cpp#L48-L97)
- [TcpClient.cpp](file://cpp\Tools\TcpClient.cpp#L101-L130)
- [TcpClient.cpp](file://cpp\Tools\TcpClient.cpp#L252-L278)
## Zmodem文件传输协议
### Zmodem类实现
`Zmodem`类实现了Zmodem协议,用于在串口连接上进行可靠的文件传输。
```mermaid
classDiagram
class Zmodem {
+CSComPort* port
+FILE* file
+long file_length
+long byte_count
+int file_count
+char file_name[128]
+char buffer[BUFFSIZE]
+unsigned long m_FileSize
+int m_ByteTransfer
+char m_WriteBuff[8192]
+int m_WriteBuffLength
+int m_nBufReadIndex
+FILE* zmodemLog
+DWORD dwBegin
+char cReadLog[2048]
+int nLogLength
+int nIndex
+Zmodem(CSComPort* rs232_port)
+~Zmodem()
+BOOL Send(char* files[])
+BOOL Receive(CString Path)
+BOOL SendCancelString()
+int GetTransferPosit()
+int GetFileSize()
}
```
**Diagram sources**
- [Zmodem.h](file://h\Zmodem.h#L31-L142)
- [Zmodem.cpp](file://cpp\Tools\Zmodem.cpp#L33-L56)
**帧结构解析**
Zmodem协议使用多种帧类型进行通信,包括`ZRQINIT``ZRINIT``ZFILE``ZDATA``ZEOF``ZFIN``ReadHeader`方法解析接收到的帧头,`SendBinaryHeader`方法发送二进制帧头。
**错误校验**
Zmodem协议使用CRC-16或CRC-32进行错误校验。`Crc16``Crc32`类用于计算数据的校验和。`SendDataFrame`方法在发送数据帧时包含校验信息。
**断点续传机制**
`ReceiveSingleFile`方法实现了断点续传功能。当接收文件时,如果文件已存在且大小与远程文件相同,则发送`ZRPOS`帧请求从上次中断的位置继续传输。
**Section sources**
- [Zmodem.cpp](file://cpp\Tools\Zmodem.cpp#L128-L127)
- [Zmodem.cpp](file://cpp\Tools\Zmodem.cpp#L558-L774)
- [Zmodem.cpp](file://cpp\Tools\Zmodem.cpp#L253-L258)
## 文件传输协作机制
### FileTransfer与FileTransfer_crul协作
`FileTransfer``FileTransfer_crul`类共同实现了设备与主机间的文件传输功能。
```mermaid
classDiagram
class FileTransfer {
+CSComPort* port
+FILE* file
+long file_length
+long byte_count
+int file_count
+char file_name[128]
+char buffer[BUFFSIZE]
+FileTransfer()
+virtual ~FileTransfer()
+virtual void error(char* fmt, ...)
+virtual void status(char* fmt, ...)
+virtual BOOL Send(char* files[]) = 0
+virtual BOOL Receive(CString Path) = 0
}
class FileTransfer_crul {
+CURL* m_pCrEasyHandl
+CURL* m_pCrPuteasyHandl
+FILE* m_pLog
+CString m_strUrl
+CString m_strSavePath
+CString m_strSaveName
+bool m_bIsNeedProgress
+bool m_bIsNeedErrInfo
+int m_nTimeOut
+bool m_bIsGobalInitial
+bool m_bIsHttpsFlag
+bool m_bIsSSLCertify
+FileTransfer_crul()
+virtual ~FileTransfer_crul()
+bool Inital()
+void UnInital()
+void SetOption(EnOptFlag enFlag, CString strInfo, bool bFlag = false)
+bool DownloadFile()
+DOUBLE GetFileSize()
+bool GetUrlResConValild()
+void InitalLogInfo()
+static size_t WriteToFile(void* ptr, size_t size, size_t nmemb, void* userp)
+static int xferinfo(void* p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
+static size_t WriteToBuff(void* ptr, size_t size, size_t nmemb, void* userp)
+static size_t WriteFuncForUrlTest(void* ptr, size_t size, size_t nmemb, void* userp)
+void PrintLog(CString strLog)
}
FileTransfer <|-- Zmodem : "extends"
FileTransfer_crul --> CURL : "uses"
```
**Diagram sources**
- [FileTransfer.h](file://h\FileTransfer.h#L26-L48)
- [FileTransfer_crul.h](file://h\FileTransfer_crul.h#L55-L110)
- [FileTransfer_crul.cpp](file://cpp\Tools\FileTransfer_crul.cpp#L24-L62)
**协作流程**
`FileTransfer`作为基类定义了文件传输的接口,`Zmodem`类继承自`FileTransfer`并实现了基于串口的文件传输。`FileTransfer_crul`类使用libcurl库实现HTTP/HTTPS文件下载功能。
**Section sources**
- [FileTransfer.cpp](file://cpp\Tools\FileTransfer.cpp#L28-L47)
- [FileTransfer_crul.cpp](file://cpp\Tools\FileTransfer_crul.cpp#L151-L281)
## HTTP下载功能
### HttpDownload类实现
`HttpDownload`类实现了HTTP协议下载功能,支持多线程下载和断点续传。
```mermaid
classDiagram
class HttpDownload {
+BOOL m_bSupportResume
+BOOL m_bResume
+BOOL m_bStop
+BOOL m_bIsUseFile
+BOOL m_bFileLocked
+BOOL m_bAddSize
+DWORD m_dwDownloadSize
+int runningThreadCnt
+int nComplete
+CString m_strServer
+CString m_strObject
+CString m_strSavePath
+CString m_strTempSavePath
+CString m_strReferer
+CString m_strDownloadUrl
+CTime m_TimeLastModified
+UINT m_nPort
+FILE* globalFile
+DWORD m_dwFileSize
+int m_dwThreadCnt
+InforImpl inforImpl
+NewTask TaskInfo
+CSocket m_pSocket
+HttpDownload()
+virtual ~HttpDownload()
+void FreeMemory()
+void SetStop(bool stop)
+BOOL StartHttpTask(NewTask task)
+bool CreateNewFile(CString fileName, DWORD size)
+void GetInfofromDevision()
+bool GetInfofromTemp(CString filename)
+void CreateInforImpl(CString str)
+UINT SendRequest(BOOL bHead = false)
+UINT GetInfo(LPCTSTR lpszHeader, DWORD& dwContentLength, DWORD& dwStatusCode, CTime& TimeLastModified)
+BOOL ParseURL(CString str)
+CTime GetTime(LPCTSTR lpszTime)
+bool WriteInfoToFile()
+bool WriteToFile(CString filePath, DWORD pos, char* buffer, int len)
+void CreateThread()
+bool Download(int index)
}
class InforImpl {
+CString server
+CString object
+CString savePath
+DWORD fileSize
+int threadCnt
+DWORD downloadSize
+FromToImpl* fromToImpl
}
class FromToImpl {
+DWORD from
+DWORD to
}
class NewTask {
+CString server
+CString object
+UINT port
+UINT threadCnt
+CString savepath
}
HttpDownload --> InforImpl : "contains"
HttpDownload --> FromToImpl : "contains"
HttpDownload --> NewTask : "uses"
```
**Diagram sources**
- [HttpDownload.h](file://h\HttpDownload.h#L69-L154)
- [HttpDownload.cpp](file://cpp\Tools\HttpDownload.cpp#L26-L46)
**多线程下载**
`StartHttpTask`方法首先发送HTTP请求获取文件大小,然后根据文件大小和线程数分配下载任务。每个线程负责下载文件的一部分,通过`Range`头指定下载范围。
**断点续传**
`GetInfofromTemp`方法从临时配置文件中读取已下载的文件信息,实现断点续传功能。如果文件大小发生变化,则重新开始下载。
**Section sources**
- [HttpDownload.cpp](file://cpp\Tools\HttpDownload.cpp#L68-L219)
- [HttpDownload.cpp](file://cpp\Tools\HttpDownload.cpp#L104-L209)
## 应用场景与调用示例
### 设备连接
```mermaid
sequenceDiagram
participant 应用程序
participant SComPort
participant 串口硬件
应用程序->>SComPort : OpenComm("COM1")
SComPort->>串口硬件 : CreateFile("\\\\.\\COM1")
串口硬件-->>SComPort : 返回句柄
SComPort->>SComPort : 设置DCB参数
SComPort-->>应用程序 : 返回TRUE
```
**Diagram sources**
- [SComPort.cpp](file://cpp\Tools\SComPort.cpp#L127-L223)
### 固件升级
```mermaid
sequenceDiagram
participant 应用程序
participant Zmodem
participant SComPort
应用程序->>Zmodem : Send(["firmware.bin"])
Zmodem->>SComPort : SendDataDirectly(ZRQINIT帧)
SComPort->>Zmodem : 接收响应
Zmodem->>Zmodem : 发送ZFILE帧
Zmodem->>SComPort : 发送数据帧
loop 数据传输
SComPort->>Zmodem : 接收数据帧
Zmodem->>SComPort : 发送ZACK确认
end
Zmodem->>SComPort : 发送ZFIN帧
SComPort->>Zmodem : 接收ZFIN响应
Zmodem-->>应用程序 : 返回TRUE
```
**Diagram sources**
- [Zmodem.cpp](file://cpp\Tools\Zmodem.cpp#L62-L127)
### 数据同步
```mermaid
sequenceDiagram
participant 应用程序
participant TcpClient
participant 服务器
应用程序->>TcpClient : ConnectToServer("192.168.1.100", 8080)
TcpClient->>服务器 : 建立TCP连接
服务器-->>TcpClient : 连接成功
TcpClient-->>应用程序 : 返回TRUE
应用程序->>TcpClient : SendData(同步数据)
TcpClient->>服务器 : 发送数据
服务器-->>TcpClient : 返回响应
TcpClient-->>应用程序 : 返回发送字节数
```
**Diagram sources**
- [TcpClient.cpp](file://cpp\Tools\TcpClient.cpp#L101-L130)
## 最佳实践
### 线程同步
使用Windows API的同步机制,如`CreateThread``WaitForSingleObject`,确保多线程环境下的数据一致性。
### 资源释放
在析构函数中释放所有分配的资源,如关闭文件句柄、套接字和线程句柄。
### 异常处理
使用`try-catch`块捕获异常,并通过日志记录详细的错误信息。
## 结论
GeomativeStudio的通信工具集成功了多种通信方式,为设备连接、固件升级和数据同步提供了可靠的基础。通过深入分析这些模块的实现机制,开发者可以更好地理解和使用这些工具,提高开发效率和系统稳定性。