“write: connection reset”表明内核已收到对端RST包,连接被强制终止,非网络抖动;主因包括端口未监听、请求帧格式错误(如MBAP长度字段不匹配)、服务崩溃或防火墙伪造RST;须用conn.Write()直传原始字节,禁用fmt.Fprintf等高层I/O。

“write: connection reset” 是对端主动发 RST 包的明确信号,不是网络抖动或超时,而是连接已被强制终止。必须检查请求合法性、设备状态和 I/O 方式,否则重试毫无意义。
为什么 write() 立即返回 connection reset
这个错误发生在 conn.Write() 调用返回时,说明内核已收到对端发来的 TCP RST 包。它不是写入失败(如缓冲区满),而是连接在协议层已被拒绝——常见原因有:
- 目标 Modbus 设备未监听该端口,或服务进程崩溃(等价于访问一个关闭的 socket)
- 发送的 Modbus TCP 请求帧格式错误(如
Length字段与实际后续字节数不符),设备解析失败后直接 RST - 设备忙或资源耗尽(如并发连接数超限),选择快速拒绝而非排队
- 防火墙/NAT 设备拦截并伪造 RST(少见但需排查)
必须用 conn.Write(),禁用 fmt.Fprintf 或 bufio.Writer
Modbus TCP 是纯二进制协议,任何高层 I/O 都可能破坏字节序列:
-
fmt.Fprintf(conn, "%s", []byte{...})会尝试格式化字节切片为字符串,若含0x00或非 UTF-8 字节,结果不可控 -
bufio.NewWriter(conn).Write(...)引入额外缓冲和 flush 时机不确定性,易导致帧被截断或拼接 - 正确做法是直接调用
conn.Write([]byte{...}),确保原始字节原样发出
如何验证请求帧是否合法
构造请求前,务必按 MBAP 头 + PDU 严格计算长度字段。例如读保持寄存器(功能码 0x03):
0x00, 0x01, // Transaction ID 0x00, 0x00, // Protocol ID 0x00, 0x06, // Length = 6 (Unit ID 1B + Func 1B + Data 4B) 0x01, // Unit ID 0x03, // Function code 0x00, 0x01, // Start address 0x00, 0x01 // Register count
重点检查:Length 字段是否等于 1 + 1 + len(data)(即 Unit ID + Func Code + Data 字节数)。错 1 字节,设备大概率 RST。
写入后立即 read 前先检查 write 错误
不要假设 Write() 成功就一定能 Read() 到响应。RST 可能在 write 返回后、read 调用前发生。务必:
- 检查
conn.Write()返回的err:若非nil且是syscall.ECONNRESET或syscall.WSAECONNRESET,立刻放弃本次事务 - 避免在 write 出错后仍调用
conn.Read(),这会触发重复 RST 或 panic - 使用
errors.Is(err, syscall.ECONNRESET)跨平台判断(Linux/macOS)或errors.Is(err, syscall.WSAECONNRESET)(Windows)
真正棘手的是设备静默丢包或中间设备干扰,此时不会报 RST,但 write 成功、read 超时。这类情况需结合抓包(Wireshark 过滤 tcp.port == 502)确认帧是否发出、是否有 RST 回复——空响应和 connection reset 是两种完全不同的故障面,不能混为一谈。


















