2. GEM通信协议基础:TCP/IP协议栈、Socket通信原理、GEM消息格式
各位好,我是老张。今天咱们聊GEM通信这块硬骨头。说实话,我刚入行那会儿,觉得协议栈这东西离应用层很远,直到第一次调试GEM联机——设备死活连不上主机,抓包一看,TCP三次握手都没完成。嗯,从那以后我老实了。
GEM通信说白了就三层:TCP/IP协议栈负责把数据从A搬到B,Socket是咱们写代码时直接调用的接口,GEM消息格式则是设备与主机之间约定好的“黑话”。这三层缺一不可,咱们一层层拆开看。
核心认知:GEM通信不是发明新协议,而是在TCP/IP之上定义了一套应用层消息规范。你只要搞懂Socket怎么收发字节流,再套上GEM的消息格式,就能跟任何SEMI标准设备对话。
2.1 TCP/IP协议栈——GEM通信的“高速公路”
GEM为什么选TCP/IP?我个人的理解是:它成熟、稳定、跨平台。你想想看,半导体工厂里设备来自天南海北,Windows、Linux、VxWorks都有,TCP/IP是它们唯一通用的“语言”。
TCP/IP协议栈分四层,GEM主要跟传输层和应用层打交道:
| 层级 | 典型协议 | GEM中的角色 |
|---|---|---|
| 应用层 | GEM/SECS-II | 定义消息格式、会话管理、状态机 |
| 传输层 | TCP | 可靠连接、数据分段、流量控制 |
| 网络层 | IP | 寻址、路由(设备IP由工厂IT分配) |
| 链路层 | 以太网 | 物理传输(通常走1000BASE-T) |
这里有个坑,我提醒一下:GEM要求使用TCP,不是UDP。为什么?因为GEM消息必须保证按序到达、不丢包。UDP虽然快,但丢了包你得自己重传,那代码复杂度就上去了。SEMI标准选TCP,说白了就是“把可靠的事交给协议栈,咱们专心搞业务”。
实战经验:我在项目里见过有人为了“性能”改用UDP+自定义重传,结果调试了两个月,最后还是换回TCP。别重复造轮子,TCP的拥塞控制和重传机制比你自己写的强得多。
2.2 Socket通信原理——代码层面的“电话线”
Socket是什么?你可以把它想象成一根电话线。设备端拨号(connect),主机端接听(accept),然后两边对着话筒说话(send/recv)。挂断(close)时,对方就知道了。
GEM通信中,设备通常作为客户端,主机作为服务器。为什么这么设计?因为设备数量多、IP不固定,主机作为服务端更容易管理连接。我习惯把Socket通信拆成四个步骤:
- 创建Socket —— 指定协议族(AF_INET)、类型(SOCK_STREAM)
- 建立连接 —— 客户端connect,服务端accept
- 收发数据 —— 用send/recv读写字节流
- 关闭连接 —— 优雅关闭,避免数据丢失
来看一段典型的GEM设备端Socket初始化代码(C语言风格):
// 设备端:创建TCP Socket并连接到主机
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
// 嗯,socket创建失败,检查权限或资源
return -1;
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(5000); // GEM默认端口5000
inet_pton(AF_INET, "192.168.1.100", &server_addr.sin_addr);
// 连接主机,超时建议设10秒
if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
// 连接失败,可能是主机没启动或网络不通
close(sock);
return -1;
}
// 连接成功,开始收发GEM消息
// ...
注意:GEM标准规定,设备必须在启动后主动连接主机。如果连接断开,设备需要自动重连。我曾经遇到一个设备,重连间隔设成了1秒,结果把主机CPU打满了。建议重连间隔至少3秒,并且加上指数退避。
2.3 GEM消息格式——设备与主机的“黑话”
TCP/IP和Socket只是管道,真正的内容是GEM消息。GEM消息格式定义在SEMI E5(SECS-II)标准中,我把它总结成三个核心概念:消息头、数据项、消息类型。
2.3.1 消息头(Message Header)
每条GEM消息都有一个10字节的固定头部,结构如下:
| 偏移 | 长度 | 字段 | 说明 |
|---|---|---|---|
| 0 | 2 | Length | 整条消息长度(包含头部) |
| 2 | 1 | Device ID | 设备ID,通常为0 |
| 3 | 1 | Stream | 流号(0-127) |
| 4 | 1 | Function | 功能号(0-255) |
| 5 | 1 | PType | 包类型,GEM固定为0 |
| 6 | 1 | SType | 会话类型,数据消息为0 |
| 7 | 2 | System Bytes | 系统字节,用于消息匹配 |
| 9 | 1 | Header Byte 9 | 备用/扩展 |
这里有个关键点:Stream和Function共同决定消息的语义。比如S1F13是“建立通信请求”,S1F14是“建立通信确认”。我刚开始记这些编号时头都大了,后来发现规律:奇数号是请求(Primary),偶数号是响应(Secondary)。
2.3.2 数据项(Data Item)
消息头后面跟着的就是数据。GEM使用一种叫SML(SECS Message Language)的格式来编码数据。说白了就是:每个数据项前面加一个类型标签和长度。
常见的SML类型:
- L —— 列表(List),可以嵌套
- B —— 二进制(Binary)
- BOOLEAN —— 布尔值
- A —— ASCII字符串
- I1/I2/I4/I8 —— 有符号整数(1/2/4/8字节)
- U1/U2/U4/U8 —— 无符号整数
- F4/F8 —— 浮点数
举个例子,一条S1F13消息(建立通信请求)的完整报文大概是这样的:
// S1F13 建立通信请求 (十六进制)
00 1A // Length = 26字节
00 // Device ID = 0
01 // Stream = 1
0D // Function = 13 (0x0D)
00 // PType = 0
00 // SType = 0
00 01 // System Bytes = 1
00 // Header Byte 9 = 0
// 数据部分:空列表
04 00 // L, 0个元素
个人习惯:调试GEM消息时,我从来不看十六进制。我会用Wireshark的SECS-II解析器,或者写个小工具把报文转成SML文本。你想想看,对着“04 00”猜意思,不如直接看“L[0]”来得痛快。
2.3.3 消息类型与事务
GEM消息分为两类:
- Primary Message(主消息) —— 发起方发送,奇数Function号
- Secondary Message(从消息) —— 响应方回复,偶数Function号
一个完整的事务就是“Primary + Secondary”配对。System Bytes字段就是用来匹配请求和响应的。设备发S1F13时带上System Bytes=1,主机回复S1F14时必须把System Bytes也设为1。这样两边都知道“哦,这条回复是对应那条请求的”。
避坑指南:我曾经犯过一个低级错误——重发消息时忘了更新System Bytes。结果主机收到两条相同System Bytes的请求,直接当成重复消息丢弃了。记住:每条新消息的System Bytes必须递增,通常用循环计数器实现。
2.4 知识体系总览
下面这张图把GEM通信的完整链路画出来了,从物理层到应用层,一目了然:
2.5 本章小结
咱们把GEM通信的底牌都翻了一遍:
- TCP/IP协议栈是基础设施,GEM站在应用层,依赖TCP的可靠性
- Socket编程是咱们写代码的入口,记住设备主动连主机,重连要加退避
- GEM消息格式由10字节头部+SML数据组成,Stream/Function决定语义,System Bytes匹配事务