go写tcp服务端必须用for循环持续accept,否则只处理一个连接就退出;每个conn需独立goroutine处理并设读超时,避免卡死或泄漏;read返回不完整是tcp流式协议正常表现,须自行定义消息边界。

Go 写 TCP 服务端,不加 goroutine 会卡死在 Accept,加了又容易泄漏或读超时;Read 返回数据不完整不是 bug,是 TCP 流式协议的正常表现;粘包不是“要避免的问题”,而是“必须自己定义边界”的前提。
net.Listen 后必须用 for 循环 Accept,不能只调一次
监听启动后,listener.Accept() 是阻塞调用,只调一次就退出,后续连接全被丢弃。常见错误是把 Accept 放在 main 函数末尾、没包进循环,或者误以为“一次收多个连接”可行——TCP 没这种批量接口。
-
net.Listen("tcp", ":8080")绑定所有网卡,"localhost:8080"只响应回环请求,连错地址会导致客户端连不上 - 端口被占时错误是
listen tcp :8080: bind: address already in use,Linux/macOS 用lsof -i :8080查,Windows 用netstat -ano | findstr :8080 -
defer listener.Close()必须写在for循环之前,否则循环还没开始 listener 就关了
每个 conn 必须用独立 goroutine 处理,但别裸 go handle(conn)
主线程里直接 handle(conn) 会阻塞后续 Accept,导致新连接无法接入;但写成 go handle(conn) 而不控制生命周期,客户端异常断开时 goroutine 可能永久挂起。
- 务必在 goroutine 入口设置读超时:
conn.SetReadDeadline(time.Now().Add(10 * time.Second)),否则空连接或慢客户端会拖垮整个服务 - 闭包捕获循环变量易出错,应显式传参:
go func(c net.Conn) { ... }(conn),而不是go handle(conn) -
handle函数末尾必须defer c.Close(),Go 不自动回收 socket 文件描述符,泄漏会耗尽系统资源
conn.Read 不保证读满 buf,n == 0 且 err == nil 是合法状态
conn.Read([]byte) 是底层 syscall read 的封装,它只返回当前内核缓冲区里已到达的字节,和你发了多少、分几次发完全无关。假设你发了 "hello\nworld\n",Read 可能一次返回 "hel",下一次 "lo\nwor",再下一次 "ld\n"——这不是故障,是流式协议本质。
- 永远检查返回值
n,只处理buf[:n],不要直接string(buf)(未读部分是脏内存) - 不要用
bufio.Scanner做协议解析除非你确认消息长度 MaxScanTokenSize 就是这个值,超长会报scanner: token too long - 换行分隔用
bufio.NewReader(conn).ReadString('\n'),但注意:连接未关闭却无新数据时它返回io.EOF和空字符串,不是阻塞等待
粘包必须靠协议层解决,没有“自动拆包”这回事
TCP 本身不定义消息边界,所谓“粘包”只是开发者对“应用层消息”有预期,而传输层只管字节流。Go 标准库不会、也不能替你判断哪几个字节是一条完整请求。
- 文本协议推荐行分隔,但发送方必须确保每条消息以
\n结尾,服务端用ReadString('\n')或手动扫描 - 二进制协议必须加长度头,例如前 4 字节存
binary.BigEndian.Uint32(len(body)),先读够 4 字节再读 body - 别在
Read后立刻Write回复,要等完整消息收齐再处理,否则可能把半截消息当有效请求执行
最常被跳过的细节是:超时设置不是可选优化,是连接管理的底线;Read 的返回长度不是提示,是唯一可信的数据边界;而“处理完就 close”这件事,比写业务逻辑还优先级高——因为文件描述符不会自己归还。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











