[SECS/GEM研究] (四)HSMS 用TCP取代串口

SECS/GEM 协议栈开发笔记 · 第 4 篇

基于代码库:Libsecs(一个 C++ 实现的 SEMI 协议栈)

说在前面:由于工作的原因,本人早些年开发了一套SEMI标准的协议栈用于公司的SECS标准实现,为了防止经验遗忘,特写下本专辑以免后续忘记。

上篇聊完 SECS-I,结尾说了为什么现在都爱用 HSMS 而不用串口。这一篇来分析和讨论下HSMS(标准编号 E37),它干的事本质上和 SECS-I 一样:把 SECS-II 那套消息从一端搬到另一端。区别在于,它下层网络采用的是 TCP/IP 网线,不是 RS-232了,天然这种方式通讯的速度会快不少。

我当年第一次把设备从串口挪到以太网上的时候,最直观的感受就是:不用再操心分块、不用再管 ENQ/ACK 那套啰嗦握手了。但后面才发现,高兴太早,HSMS 自己也是有一套"链接怎么建、心跳怎么跳、断了怎么办"的规矩,但是这套规矩比串口那套更贴近网络现实。


一、为什么要用 TCP 取代串口

串口那套在老车间里确实皮实,但随着设备变复杂,我猜老外在处理通讯问题时也是发现了很多的弊端,所以才催生了HSMS这个标准:

  1. 速率天花板低。9600、19200 波特率,碰上要传大量 trace 数据(后面 GEM 篇会讲)基本是便秘哈,这个速度完全扛不住。
  2. 距离短、拓扑死。RS-232 十几米就到头,想跨厂房、跨楼基本没戏。
  3. 半双工效率差。上篇说的 ENQ/ACK 轮流说话,本质是牺牲速率换稳定。

而现代化的工厂网络都是以太网,TCP 自带可靠传输(丢包重传、保序、流量控制全包了),带宽管够、距离随便拉。HSMS 的思路也很明确:既然 TCP 已经把"可靠"这件事干完了,那我就不重复造轮子,直接把 SECS-II 消息套个简单外壳扔到 TCP 流上就行。所以 HSMS 在 SEMI 分层里跟 SECS-I 是平级的------都是"传输层",二选一,上面都接 SECS-II。

这里要科普下,很多初次接触SEMI协议的人会误认为SECS-I和SECS-II是同一个东西,后者只是前者的升级版本,其实不然,大错特错了,兄弟😭


二、HSMS 的连接模型:谁拨号、连到哪

HSMS 跑在 TCP 上,先得有连接。这里有个概念叫"连接模式",我在Libsecs 里定义成一个枚举:

cpp 复制代码
typedef enum{ PASSIVE, ACTIVE } ConnectMode;
  • ACTIVE(主动):自己主动去连对方 IP:端口,像拨号。
  • PASSIVE(被动):自己开个端口听着,等对方连过来,像接电话。

Libsecs 默认给的是 ACTIVE,也就是设备默认主动去拨 remote_ip:remote_port。

一般而言,现场部署的时候,更常见的搭配是 host(EAP/MES)当 ACTIVE 主动连设备,设备当 PASSIVE 监听

HSMS 标准没规定非得谁拨号,这纯粹是个可配置项,只是行业习惯这么摆。你把两边角色配反了也能通,只要一端 active、一端 passive 对上就行。

顺带一个概念:session(会话) 。一条 TCP 连接上可以开多个 session,靠消息头里的 session_id 区分。也就是说,HSMS 支持"一根网线、多路逻辑通道",这是串口那种一对一物理链路做不到的。Libsecs 的 HsmsParam 里 device_id、remote_ip、remote_port、local_ip、local_port 就是把"连到哪、我是谁"全收在一个结构体里描述:

cpp 复制代码
typedef struct {
    ConnectMode role;
    uint16_t device_id;
    int remote_port;  
    std::string remote_ip;
    int local_port;    
    std::string local_ip;
    uint16_t T3, T5, T6, T7, T8;
    uint16_t TestTime;   // 心跳周期
}HsmsParam;

三、建链握手:TCP 连上 ≠ 能通信

连上 TCP 只是第一步。HSMS 规定连上之后还要走一个 SELECT 过程,设备才算"在线可通信"。状态机就三个:

text 复制代码
NOT_CONNECTED  ------ TCP 连上 ------>  NOT_SELECTED
NOT_SELECTED   ------ 收到 SELECT_REQ/回 SELECT_RSP ------>  SELECTED
SELECTED       ------ 收到 DESELECT_REQ ------>  NOT_SELECTED
SELECTED       ------ 收到 SEPARATE ------>  NOT_CONNECTED(直接断开)

简单的来说,HSMS处理上面的状态机逻辑为:对方发来 SELECT_REQ,只要当前是 NOT_SELECTED,就回 SELECT_RSP 带上状态字 0x00(Communication Established),状态切到 SELECTED;如果我已经 SELECTED 了又收到 SELECT_REQ,就回 0x01(Communication Already Active,意思是"咱俩早通了,别重复建")。DESELECT 反过来,把状态退回 NOT_SELECTED。

下面这张图是一条典型的 active 设备通信时序:

text 复制代码
[设备 ACTIVE]                [Host/PASSIVE]
     |                            |
     |------ TCP 连接请求 ------->|
     |<----- 连接建立 ------------|
     |------ SELECT_REQ -------->|
     |<----- SELECT_RSP(0x00) ---|   状态: SELECTED
     |                            |
     |==== 数据消息(SxFy) =======>|   ← 真正干活从这里开始
     |<=== 数据消息(回复) ========|
     |                            |
     |<----- LINKTEST_REQ -------|   心跳
     |------ LINKTEST_RSP ------>|
     |                            |
     |<----- SEPARATE -----------|   断链
     |     (连接关闭)            |

注意 SELECT 之前,谁都不能发数据消息,只能发控制消息(SELECT/LINKTEST 这类)。这和 SECS-I 很像:串口上 ENQ 握手完才能发块;HSMS 上 SELECT 完才能发数据。只不过 HSMS 把"握手"从物理线路搬到了逻辑连接上。


四、心跳 Linktest 与断链 SEPARATE

网络跟串口不一样,串口线拔了你能立刻知道,网线"半断不断"(比如对端进程挂了但 TCP 没正常关闭)时,连接看起来还在,其实早死透了。HSMS 用两个机制对付这个:

  • Linktest(心跳) :双方定期互相发 LINKTEST_REQ,对方回 LINKTEST_RSP,证明"我还活着"。周期由 TestTime 控制,我在Libsecs的代码里就是收到 LINKTEST_REQ 直接回一个 LINKTEST_RSP,纯保活,不带业务数据。
  • Separate(断链):哪一方想干净地结束,发个 SEPARATE 就行,对方收到立刻关连接,状态回 NOT_CONNECTED。比直接拔网线优雅,至少能让对端有机会做清理。

五、HSMS 的头,和 SECS-I 的头不一样在哪

这是我觉得最该讲清的一点,因为很多人第一次看会懵:同样是 10 字节消息头,HSMS 和 SECS-I 长不一样。

SECS-I 的块头(10 字节):

text 复制代码
session_id(2) | stream+W(1) | func(1) | block_h+E(1) | block_l(1) | system(4)

HSMS 的消息头(也是 10 字节):

text 复制代码
session_id(2) | stream+W(1) | func(1) | PType(1) | SType(1) | system(4)

看出差别没?第 5、6 字节,SECS-I 放的是 block 序号(带 E-bit 末块标志) ,因为串口要分块;HSMS 放的是 PType(协议类型,固定 0x00=SECS-II)+ SType(消息类型)

HSMS的SType可以枚举如下:

cpp 复制代码
typedef enum {
    DATA        = 0x00,   // 数据消息(真正的 SxFy)
    SELECT_REQ  = 0x01,
    SELECT_RSP  = 0x02,
    DESELECT_REQ= 0x03,
    DESELECT_RSP= 0x04,
    LINKTEST_REQ= 0x05,
    LINKTEST_RSP= 0x06,
    REJECT      = 0x07,
    SEPARATE    = 0x09,
}SType;

也就是说,HSMS 靠 SType 这一个字节,就把"这是业务数据还是控制指令"区分开了。这也解释了为什么 HSMS 不需要分块:TCP 是可靠字节流,它直接在消息最前面放 4 字节长度前缀来切分边界,一条消息多大都能一次发完,根本不用像串口那样剁成 254 字节的块。

对照 SECS-I:它没有那 4 字节长度前缀(串口靠 ENQ/ACK 和块边界定界),反而多了 block 序号和 E-bit。一个为"剁块"服务,一个为"定界"服务,分工不同。


六、超时参数:比标准更"急"

HSMS 定义了一组超时:

参数 含义 协议栈默认
T3 等回复超时(一条消息发出去多久没回算失败) 10s
T5 连接超时(建 TCP 连多久没成算失败) 5s
T6 控制消息超时(SELECT/LINKTEST 等多久没回应) 5s
T7 未选中超时(连上了但一直不 SELECT,多久后踢掉) 10s
T8 网络空闲超时(多久没任何消息往来就认为死链) 10s
TestTime 心跳周期 5s

HSMS 标准上 T3 推荐 45 秒,实际上一般来说 10 秒差不多了。


七、怎么去实现HSMS?

为了去实现HSMS ,我在Libsecs 里是这样组织的:

  • HsmsEquipment :设备对象,同样继承自通用的 SecsEquipment。它在 setParam 时看 role 是 ACTIVE 还是 PASSIVE,当场 new 出 HsmsEquipmentActiveHsmsEquipmentPassive------也就是"主动拨号"和"被动监听"两套实现分文件写,互不纠缠。
  • handleMessage:所有控制消息的调度中枢。收到 SELECT/DESELECT/LINKTEST/SEPARATE,在这里用 switch 分派:该回什么的回什么,该切状态的切状态,该关连接的关连接。数据消息(SType=DATA)则往下交给 SECS-II 层去解析。
  • serializeMessage :把一条 SecsMessage 编成"4 字节长度 + 10 字节头 + 数据体"的字节流丢进 TCP。
  • HsmsParser:对端来的字节流,按 4 字节长度前缀把一条条消息切出来,再按 SType 还原成数据消息或控制消息。

这一套和上篇 SECS-I 的 Secs1Equipment / Secs1Protocol / Secs1Parser 三件套是对应的------只是底层从"串口 + ENQ/ACK 状态机"换成了"TCP socket + SELECT/LINKTEST 状态机"。上层的 SECS-II 消息体、事务管理完全不用动。这也侧面说明一个好的SEMI协议栈设计一定是:传输层可替换,业务层不动。


八、小结

HSMS(E37)这一层记住这几句:

  1. 它和 SECS-I 平级,都是传输层,只是脚下踩 TCP/IP 而非串口;
  2. 谁拨号可配(ACTIVE/PASSIVE),连上 TCP 还得 SELECT 才算 SELECTED 能通信;
  3. 用 Linktest 当心跳、Separate 干净断链,专治网络"假死";
  4. 消息头用 SType 区分数据/控制,靠 4 字节长度前缀定界,不分块------这是和 SECS-I 最直观的差异;

下一篇一起来讨论下 SECS-II(E5),也就是消息"里面到底装了什么"。前面这三层(SECS-I / HSMS / 事务)管的是"怎么把字节可靠送过去",从下篇开始才真正碰到数据------那 12 种数据类型(List、ASCII、各种整数浮点)、嵌套结构、还有 SxFy 这套编号到底怎么读。个人觉得 SECS-II 才是整个协议栈里最值得花篇幅的一块,下篇详细分析。


本人不是 SEMI 专家,上面这些也是当年啃 E37 文档一步一步进行理解的,如果哪里理解有偏差,评论区直接拍我。

相关推荐
三言老师1 小时前
CentOS7 / 8 yum 查询软件安装路径实操
运维·服务器·网络·centos
倚栏听雨_ylty2 小时前
H3C交换机关闭Telnet和HTTP服务实例
运维·网络·tcp/ip·网络安全
xywww1682 小时前
Claude Opus 5 API 接入实战:国内项目上线前的网络、Key、限流和排错清单
大数据·linux·网络·数据库·云计算·aws
ShineWinsu2 小时前
对于Linux:http的解析
linux·网络·c++·网络协议·http·请求·响应
白猫不黑2 小时前
SQL注入实战:手工注入全流程详解
网络·数据库·sql·web安全·网络安全·信息安全
choumin11 小时前
创建型模式——工厂方法模式
c++·设计模式·工厂方法模式·创建型模式
云小逸12 小时前
【SVN 详细使用指南:从入门到团队协作】
c++·svn
可爱系程序猿15 小时前
Windows 打印链路诊断:从设备枚举、TCP/IP 端口到 Spooler 服务恢复
网络·网络协议·tcp/ip
ziguo112217 小时前
深入浅出 C/C++ 数据类型:从入门到踩坑
linux·c语言·c++·windows·visual studio