3. HSMS 通信机制:TCP/IP 连接建立、会话管理与消息分帧
HSMS(High-Speed SECS Message Services)是 SECS/GEM 标准中基于 TCP/IP 的通信层协议。它替代了传统的 RS-232 串口通信,为半导体设备提供了高吞吐、低延迟的消息传输能力。本章深入剖析 HSMS 的核心通信机制,包括连接建立、会话管理、心跳保活以及消息的分帧与重组,这是构建高性能设备端消息解析引擎的基础。
3.1 TCP/IP 连接建立与状态机
HSMS 连接建立遵循一个有限状态机模型,确保通信双方在正确的状态下收发消息。设备端通常作为 被动方(Passive) 或 主动方(Active) 发起连接。
3.1.1 连接状态机
HSMS 定义了三个核心状态:未连接(Not Connected)、已连接(Connected) 和 已选择(Selected)。只有处于 Selected 状态时,才能传输 SECS-II 数据消息。
3.1.2 连接建立流程
- TCP 三次握手: 设备端(Active)向主机(Passive)发起 TCP 连接。主机监听指定端口(默认 5000)。
- 连接确认: TCP 连接建立后,双方进入 Connected 状态。此时仅能交换 HSMS 控制消息,不能传输数据。
- Select 请求: 设备端发送
Select.req消息,请求建立会话。 - Select 响应: 主机回复
Select.rsp,若返回0x00(成功),则进入 Selected 状态。
3.2 会话管理:Select / Deselect / Reject
会话管理是 HSMS 控制连接的核心,它通过一组控制消息来管理通信双方的逻辑会话。
3.2.1 控制消息类型
| 消息类型 | Session ID | 功能描述 | 典型响应 |
|---|---|---|---|
| Select.req | 0xFFFF | 请求建立会话,将连接从 Connected 提升至 Selected | Select.rsp |
| Select.rsp | 0xFFFF | 响应 Select 请求,返回状态码(0x00 成功) | — |
| Deselect.req | 0xFFFF | 请求释放当前会话,回到 Connected 状态 | Deselect.rsp |
| Deselect.rsp | 0xFFFF | 响应 Deselect 请求 | — |
| Reject.req | 0xFFFF | 拒绝无效或无法处理的消息 | 无(单向通知) |
3.2.2 Select 握手细节
Select 请求的 Session ID 固定为 0xFFFF,表示这是一个控制消息。设备端发送 Select.req 后,必须在 T3 超时(默认 45 秒)内收到 Select.rsp。如果超时未收到,设备端应重试或断开连接。
// HSMS Select.req 消息结构示例(16 字节头部)
// 字节 0-1: Message Length (0x0000 000A) // 10 字节
// 字节 2-3: Session ID (0xFFFF) // 控制消息
// 字节 4: Header Byte 2 (0x00)
// 字节 5: Header Byte 3 (0x00)
// 字节 6: PType (0x00) // HSMS 控制消息
// 字节 7: SType (0x01) // Select.req
// 字节 8-9: System Bytes (0x0001) // 事务 ID
// 字节 10-15: 无数据体(控制消息无数据段)
3.3 心跳机制:Linktest
HSMS 使用 Linktest 消息来检测 TCP 连接的健康状态,防止因网络故障导致“死连接”占用资源。
3.3.1 Linktest 流程
- 发起方: 通常是设备端或主机,在空闲时间超过 T6 超时(默认 5 秒)后,发送
Linktest.req。 - 响应方: 收到
Linktest.req后,必须立即回复Linktest.rsp。 - 超时处理: 如果发起方在 T3 超时 内未收到响应,则认为连接已断开,主动关闭 TCP 连接并尝试重连。
3.3.2 心跳间隔优化建议
| 参数 | 默认值 | 优化建议 | 说明 |
|---|---|---|---|
| T6 (心跳间隔) | 5 秒 | 3 ~ 10 秒 | 根据网络延迟调整,避免过于频繁增加 CPU 开销 |
| T3 (响应超时) | 45 秒 | 10 ~ 60 秒 | 必须大于网络最大往返时间,防止误判 |
| 重连次数 | 无限 | 3 ~ 5 次 | 避免无限重连导致资源耗尽,建议指数退避 |
3.4 消息分帧与重组
HSMS 消息在 TCP 流式传输中必须进行 分帧(Framing),接收方则需要进行 重组(Reassembly)。这是设备端消息解析引擎最核心的性能瓶颈之一。
3.4.1 消息帧格式
每个 HSMS 消息由 16 字节头部 和 可变长度数据体 组成。头部前 4 字节包含消息长度信息。
// HSMS 消息帧结构
// +--------+--------+--------+--------+--------+--------+--------+--------+
// | Message Length (4 bytes) | Session ID (2 bytes) | Hdr Byte2 | Hdr Byte3 |
// +--------+--------+--------+--------+--------+--------+--------+--------+
// | PType | SType | System Bytes (4 bytes) | [Data Body ...] |
// +--------+--------+--------+--------+--------+--------+--------+--------+
//
// Message Length: 表示后续数据体(包括头部剩余 12 字节 + 数据体)的总长度。
// 例如:Message Length = 12 + N,其中 N 为数据体字节数。
3.4.2 分帧与重组策略
由于 TCP 是流协议,一次 recv() 调用可能收到 半包 或 粘包。设备端必须实现一个健壮的分帧缓冲区。
- 接收缓冲区: 使用环形缓冲区或链式缓冲区暂存 TCP 数据。
- 长度预读: 先读取前 4 字节,解析出
Message Length。 - 完整帧判断: 检查缓冲区中是否已累积
Message Length字节的数据。 - 帧提取: 如果完整,则从缓冲区中切出一个完整的 HSMS 消息帧;否则继续等待数据。
- 零拷贝优化: 在 C++ 实现中,使用指针偏移或
std::string_view避免数据拷贝。
3.4.3 高性能分帧实现要点
- 避免逐字节解析: 使用
memcpy或直接内存映射读取长度字段,减少循环开销。 - 批量处理: 一次
recv()可能包含多个完整帧,应在一次处理循环中提取所有帧,减少系统调用。 - 内存池: 预分配固定大小的消息缓冲区,避免频繁
malloc/free。
// C++ 高性能分帧核心逻辑(伪代码)
class HsmsFramer {
std::vector<char> buffer_;
size_t head_ = 0;
public:
// 返回解析出的完整消息列表
std::vector<HsmsMessage> feed(const char* data, size_t len) {
buffer_.insert(buffer_.end(), data, data + len);
std::vector<HsmsMessage> messages;
while (true) {
if (buffer_.size() - head_ < 4) break; // 长度字段不足
uint32_t msg_len = ntohl(*(uint32_t*)(buffer_.data() + head_));
if (buffer_.size() - head_ < msg_len) break; // 半包
// 提取完整帧
messages.emplace_back(buffer_.data() + head_, msg_len);
head_ += msg_len;
}
// 清理已处理数据
if (head_ > 0) {
buffer_.erase(buffer_.begin(), buffer_.begin() + head_);
head_ = 0;
}
return messages;
}
};
recvmsg 配合 MSG_PEEK 标志位预读长度,避免数据拷贝。但在高并发场景下,建议使用简单的缓冲区 + 内存拷贝策略,因为 MSG_PEEK 的内核开销可能更高。