Go TCP服务需正确使用net.Listen和net.Dial,监听地址必须含端口(如":8080"),每个conn须用goroutine处理并defer关闭;Read/Write要循环或用bufio防丢包、粘包,设超时避免卡死。

net.Listen 和 net.Dial 是起点,但光会调用这两个函数不等于能写出可用的 TCP 服务——真正卡住人的从来不是“连不连得上”,而是连接建立后数据怎么收、怎么发、怎么不丢、怎么不卡死。
net.Listen 启动服务器时常见的地址写法错误
监听地址字符串必须包含端口,且格式要严格。写成 "8080" 或 "localhost:8080"(没加协议前缀)都会直接 panic:dial tcp: address 8080: missing port in address。
- 正确写法只有两种主流形式:
":8080"(监听所有本地 IP)、"127.0.0.1:8080"(仅限回环) -
defer listener.Close()必须加,否则进程退出后端口仍被占用,下次启动报address already in use - 不要在
Accept()外层用log.Fatal处理错误,客户端断连、网络抖动都会触发Accept错误,应continue而非退出整个服务
每个 conn 必须用 goroutine 单独处理
TCP 连接是全双工字节流,conn.Read 是阻塞调用。如果不用 goroutine 并发处理,第二个客户端连上来就会被第一个连接的读操作卡住,永远等不到自己的 Read 返回。
- 典型结构是:
for { conn, _ := listener.Accept(); go handleConnection(conn) } -
handleConnection函数内必须defer conn.Close(),否则连接泄漏,文件描述符耗尽后新连接直接被拒绝 - 别在
handleConnection里直接用fmt.Scanln或bufio.NewReader(os.Stdin)——那是控制台输入,不是网络连接
Read/Write 不等于“发一整条消息”
TCP 没有消息边界,conn.Write([]byte("hello\n")) 发出去的只是字节流;conn.Read(buf) 可能只读到前 3 字节,也可能一次读到两条拼在一起的消息(粘包)。这不是 bug,是协议特性。
立即学习“go语言免费学习笔记(深入)”;
- 简单场景可用
bufio.Scanner配合Split(bufio.ScanLines)按换行解析 - 生产环境建议自定义协议头:4 字节长度前缀 + 实际 payload,用
binary.Read先读长度再读内容 -
conn.SetReadDeadline必须设,否则客户端异常断开后,服务端Read会永久阻塞,goroutine 泄漏
net.Dial 客户端超时控制不能靠默认行为
net.Dial("tcp", "127.0.0.1:8080") 默认无超时,目标宕机或防火墙拦截时,可能卡住 30 秒以上才返回错误,这在 CLI 工具或微服务调用中不可接受。
- 用
&net.Dialer{Timeout: 3 * time.Second}显式控制连接建立耗时 - 连接建立后,
conn.SetDeadline和SetReadDeadline也要设,防止Read卡死 - 别忽略
conn.Write的返回值:如果对方已关闭连接,Write可能立刻返回broken pipe,需及时Close
TCP 在 Go 里确实封装得很薄,net.Conn 就是个接口,背后是系统调用。真正难的是对连接生命周期、字节流语义、并发安全和错误恢复的理解——这些不会出现在 go run main.go 成功那一刻,而是在压测时连接数飙高、客户端网络闪断、日志里突然出现大量 i/o timeout 时才浮出水面。


















