net.Listen 创建 TCP 服务器需配合 Accept 循环和 goroutine 处理连接,监听地址须带 "tcp:" 前缀,连接需设读写超时并正确处理 EOF。

怎么用 net.Listen 创建 TCP 服务器
Go 的 net.Listen 是启动 Socket 服务的入口,不是“配置完就能跑”,它只负责监听,后续必须显式调用 Accept 才能拿到连接。很多人写完 Listen 就以为服务起来了,结果 telnet 连不上——其实是没写 Accept 循环。
- 必须用
for循环持续调用listener.Accept(),否则只处理第一个连接就退出 - 每个连接建议起 goroutine 处理,否则阻塞后续连接:
go handleConn(conn) - 监听地址格式是
"tcp:127.0.0.1:8080",不能漏掉tcp:前缀,否则 panic 报错listen tcp: address 127.0.0.1:8080: missing protocol scheme - 端口被占用时,错误是
listen tcp :8080: bind: address already in use,别硬等,先lsof -i :8080或netstat -anv | grep 8080查进程
如何安全地读写 conn(避免 read/write timeout)
直接对 net.Conn 调 Read / Write 很容易卡住或丢数据,因为默认没有超时控制,且 TCP 是流式协议,不保证一次 Read 就读完一整条消息。
- 务必设置读写超时:
conn.SetReadDeadline(time.Now().Add(5 * time.Second)),否则客户端断连、发一半就挂,服务端会永久阻塞在Read - 不要假设
Read一次返回完整业务包;常见做法是加长度头(4 字节 int32 表示后续 payload 长度),先读够头再读正文 -
Write不等于发送成功,它只把数据拷进内核 socket buffer;要确认对方收到,得靠上层协议 ACK 或应用层应答机制 - 关闭连接前记得
conn.Close(),但别在 goroutine 里 close 后还继续Read—— 会返回io.EOF或use of closed network connection
UDP 场景下用 net.ListenUDP 还是 net.DialUDP
net.ListenUDP 是服务端收包入口,net.DialUDP 是客户端主动发包用的,二者底层都是 *UDPConn,但语义和使用方式差很远,混用会导致逻辑混乱甚至 panic。
- 服务端必须用
net.ListenUDP("udp", &net.UDPAddr{Port: 9999}),然后循环ReadFromUDP接收任意来源的数据 - 客户端如果只是单向发包(比如日志上报),用
net.DialUDP更方便,它返回的UDPConn自带目标地址,直接Write即可,不用每次传UDPAddr - UDP 没有连接状态,所以
DialUDP不会真正“建连”,也不会报 “connection refused”;目标端口没程序监听时,发包不报错,但对方根本收不到 - UDP 包大小受限于 MTU(通常 1500 字节),超过会分片;接收方
ReadFromUDP缓冲区不够大会直接截断,务必确保buf足够大(如make([]byte, 65536))
为什么 conn.Read 返回 0 字节却不报错
这是 TCP 半关闭的正常表现:对方调了 close() 或 shutdown(SHUT_WR),本端 Read 到 EOF,返回 n == 0 且 err == nil。新手常误判为“空数据”继续处理,导致逻辑异常。
立即学习“go语言免费学习笔记(深入)”;
- 判断连接关闭的唯一可靠方式是:
n == 0 && err == nil,此时应清理资源、退出 goroutine - 不要依赖
err != nil来判断断连;网络抖动可能只让某次Read返回临时错误(如timeout),而连接仍有效 - 如果业务协议允许长连接复用,需设计心跳或消息边界标识;否则单靠
Read返回值无法区分“空包”和“连接结束”
Socket 编程真正的复杂点不在 API 调用,而在连接生命周期管理:什么时候 accept、什么时候 close、怎么应对半开连接、如何防止 goroutine 泄漏。这些没法靠一个函数解决,得在每处 conn 使用路径上想清楚它的来龙去脉。



















