必须为每个连接分离读写goroutine——因net.Conn非并发安全,共用Read/Write会引发数据错乱、粘包、panic;正确做法是读goroutine专责读取解析并发送至writeChan,写goroutine专责从writeChan取数据写入conn。

每个连接必须拆成独立的读 goroutine 和写 goroutine,不能共用一个 conn.Read() 或 conn.Write() 调用点——这是防阻塞和数据错乱的底线。
为什么不能多个 goroutine 同时调用 conn.Read() 或 conn.Write()
net.Conn 的底层实现不是为并发读写设计的:Read() 和 Write() 共享 conn 内部状态(如缓冲区游标、EOF 标志、错误计数器)。两个 goroutine 同时 Read() 可能导致部分字节被跳过或重复解析;同时 Write() 则可能把两段数据拼成乱序包,甚至触发 write: broken pipe 或 use of closed network connection panic。
常见错误现象:
- 客户端收不到完整消息,或收到截断/粘包数据
- 服务端日志频繁出现
read tcp …: i/o timeout但连接未断 - 高并发下 goroutine 数量持续上涨,pprof 显示大量 goroutine 卡在
conn.Read()或conn.Write()
正确做法是明确职责分离:
- 一个 goroutine 专责
conn.Read()→ 解析消息 → 发送到writeChan - 另一个 goroutine 专责从
writeChan接收 → 调用conn.Write() -
conn.Close()由读 goroutine 在 EOF 或 error 后触发,并关闭writeChan
用带缓冲 channel 解耦读写,避免写 goroutine 阻塞
如果 writeChan 是无缓冲的,而远端接收慢或网络抖动,写 goroutine 会在 ch 处永久阻塞,进而导致读 goroutine 发送失败后无法退出,最终堆积 goroutine。
缓冲大小要兼顾内存与背压:
- 缓冲太小(如
make(chan []byte, 1)):几乎等同于无缓冲,起不到解耦作用 - 缓冲太大(如
make(chan []byte, 10000)):内存占用高,且延迟不可控,极端情况会 OOM - 推荐值:
make(chan []byte, 64)~make(chan []byte, 256),适用于大多数实时通信场景
注意:缓冲 channel 不解决“远端不收”的根本问题,只是把阻塞从写 goroutine 转移到生产者(读 goroutine)。所以仍需配合超时和背压策略——比如读 goroutine 检测到 writeChan 已满时主动断连,或丢弃低优先级消息。
必须设 Read/Write Deadline,否则 goroutine 泄漏不可避免
不设 deadline 的 conn.Read() 会一直阻塞,哪怕客户端静默断网。Go 不会自动感知 TCP keepalive 状态,也不会回收这种“挂起”的 goroutine。
关键配置:
-
conn.SetReadDeadline(time.Now().Add(30 * time.Second)):每次Read()前重置,实现空闲超时 -
conn.SetWriteDeadline(time.Now().Add(5 * time.Second)):防止大文件或批量消息卡死写 goroutine - 整个连接处理流程用
context.WithTimeout(ctx, 5*time.Minute)包裹,超时后强制 close conn 并清理关联资源(如从map[net.Conn]*ClientConn中删除)
别依赖 SetKeepAlive 替代 ReadDeadline:它只影响底层 TCP 连接探测,不中断阻塞中的 Read() 调用。
bufio.Writer 用在 socket 写入时要格外小心
很多人想用 bufio.NewWriter(conn) 来减少系统调用,但这是危险操作:bufio.Writer 的 Flush() 是同步阻塞的,一旦网络卡顿,Flush() 就会卡住整个写 goroutine,失去 channel 解耦的意义。
更稳妥的做法是:
- 直接使用
conn.Write(),靠 channel 缓冲 + 单写 goroutine 控制节奏 - 若真要用 bufio,必须确保:
Flush()调用也包裹在SetWriteDeadline之后,并捕获os.ErrDeadlineExceeded主动断连 - 绝对不要在写 goroutine 里对同一个 conn 混用
conn.Write()和bufio.Writer.Write(),底层缓冲会冲突
Unix domain socket 场景下还要额外注意:bufio.Writer 的缓冲行为在 UDS 上更容易引发“消息未发出却无报错”的假成功现象,务必显式 Flush() 并检查返回值。


















