“write: connection reset”表示对端已发rst包,连接被协议层拒绝,重试无效;需检查modbus设备端口监听、进程状态、mbap length字段准确性、资源限制及防火墙拦截,并严格使用conn.write([]byte{})发送原始字节。

“write: connection reset” 不是网络抖动,而是对端已发 RST 包,连接在协议层被拒绝。重试没用,必须先定位设备侧原因。
为什么 conn.Write() 立即返回 connection reset
这个错误出现在 conn.Write() 返回时,说明内核已收到对端 TCP RST 报文——连接已被强制终止,不是写失败,也不是超时。常见触发点包括:
- 目标 Modbus 设备未监听该端口,或服务进程已崩溃(等价于向一个已关闭的 socket 发送数据)
- 发送的请求帧 MBAP 中
Length字段与实际后续字节数不一致(例如少算 1 字节),设备解析失败后直接 RST - 设备资源耗尽(如并发连接数超限、寄存器访问越界),选择快速拒绝而非排队或返回异常响应
- 中间防火墙/NAT 主动拦截并伪造 RST(较少见,但需用
tcpdump排查)
必须用 conn.Write([]byte{}),禁用 fmt.Fprintf 和 bufio.Writer
Modbus TCP 是纯二进制协议,任何高层 I/O 都可能破坏原始字节序列:
-
fmt.Fprintf(conn, "%s", []byte{0x00, 0xff})会尝试将字节切片格式化为字符串,遇到0x00或非 UTF-8 字节时行为不可控 -
bufio.NewWriter(conn).Write()引入缓冲和 flush 时机不确定性,可能导致帧被截断、拼接或延迟发送,设备收不到完整 MBAP 头 - 正确做法是直接调用
conn.Write([]byte{...}),确保构造好的字节原样发出,不增不减、不转义、不缓冲
如何验证请求帧是否合法(重点检查 Length 字段)
以读保持寄存器(功能码 0x03)为例,标准 MBAP + PDU 结构为:
0x00, 0x01, // Transaction ID<br>0x00, 0x00, // Protocol ID<br>0x00, 0x06, // Length = 6 (Unit ID 1B + Func 1B + Data 4B)<br>0x01, // Unit ID<br>0x03, // Function code<br>0x00, 0x01, // Start address<br>0x00, 0x01 // Register count
关键校验逻辑:
-
Length字段必须严格等于1 + 1 + len(data)(即 Unit ID + Func Code + Data 字节数) - 错 1 字节,多数工业设备会直接 RST,不返回任何响应
- 建议在构造完请求后,用
len(requestBytes[6:])动态计算并断言requestBytes[4:6]是否匹配
写入后不能跳过错误检查就直接 read
不要假设 conn.Write() 成功就能安全调用 conn.Read()。RST 可能在 write 返回后、read 调用前发生:
- 务必检查
conn.Write()的err:若为syscall.ECONNRESET(Linux/macOS)或syscall.WSAECONNRESET(Windows),立刻中止本次事务 - 避免在 write 出错后仍调用
conn.Read(),这可能触发 panic 或重复 RST - 跨平台判断推荐:
errors.Is(err, syscall.ECONNRESET) || errors.Is(err, syscall.WSAECONNRESET)
最易被忽略的是:Length 字段的校验必须在代码里硬编码逻辑,而不是靠“我记得没错”;RST 错误必须在 write 后立刻处理,而不是留到 read 阶段才感知——此时连接已不可用,再读只是徒增风险。











