tcp回显服务器需注意:listenandserve阻塞、accept后须立即处理连接、监听地址用":端口"而非"localhost:端口"、read不保证读满、需设readdeadline防goroutine泄漏、避免粘包需自定义协议。

listenAndServe 会阻塞,别把它放在 goroutine 里还等着返回
很多人一上来就写 go http.ListenAndServe(...),结果发现服务没起来、也没报错——因为 http.ListenAndServe 是阻塞调用,但 TCP 回显服务器不用它。Go 的标准库没有内置的 net.TCPListener 高阶封装,得用 net.Listen("tcp", addr) 手动监听。一旦 listener.Accept() 返回连接,就必须立刻处理或转给 goroutine,否则后续连接会被卡住。
-
net.Listen("tcp", ":8080")失败时直接 panic 或忽略,会导致程序静默退出;务必检查 error - 不要在
for循环里重复调用net.Listen,它只该执行一次 - 客户端断开时,
conn.Read会返回io.EOF,不是错误,别当成异常打日志
Read 和 Write 必须配对,且注意缓冲区大小和阻塞行为
TCP 是字节流,conn.Read() 不保证读满你给的切片,哪怕客户端发了 1024 字节,它也可能只读到前 5 字节就返回(尤其在高并发或网络抖动时)。回显逻辑不能假设“一次 Read 就拿到完整请求”。
- 用固定大小缓冲区(如
make([]byte, 1024))比用bytes.Buffer更轻量,适合简单回显 -
conn.Write(b)可能只写出部分数据,但标准库的Write实现通常会循环写完,可不手动处理;不过仍要检查返回的n, err - 如果想支持多行输入,别依赖
\n自动分包——TCP 没消息边界,得自己定义协议(比如长度前缀),否则粘包会让回显错乱
goroutine 泄漏比想象中容易:每个 conn 必须有明确生命周期
每接受一个连接就起一个 goroutine 处理是常规做法,但若客户端异常断连、或连接长时间空闲,而你的 handler 没设超时,这个 goroutine 就永远卡在 Read 上,积少成多直接 OOM。
- 用
conn.SetReadDeadline()给每次Read加超时,比如 30 秒无数据就关闭连接 - 避免在 handler 里启动子 goroutine 却不等它结束(例如日志异步发送但没做 done channel 控制)
- 不要用全局 map 存 conn 或 goroutine id 来“管理”,Go 的 net.Conn 本身不支持取消,关连接才是唯一可靠清理方式
本地测试时 telnet 连不上?先看端口是否被占用、防火墙是否放行
写完跑起来,telnet localhost 8080 显示 Connection refused,90% 不是代码问题。Go 默认绑定 127.0.0.1,而有些系统 telnet 默认走 IPv6,或者 Docker 环境里没暴露端口。
立即学习“go语言免费学习笔记(深入)”;
- 启动服务后,用
lsof -i :8080(macOS/Linux)或netstat -ano | findstr :8080(Windows)确认进程确实在监听 - 把监听地址改成
:8080(即net.Listen("tcp", ":8080"))才能接受所有接口的连接,localhost:8080和127.0.0.1:8080都能连上 - 某些 IDE(如 VS Code 的 Go 插件)会自动启用调试器端口,可能占掉 8080,换端口更省事
回显服务器看着简单,但连接管理、超时控制、错误归因这三块最容易在压测或线上环境突然暴露。别迷信“几行代码搞定”,真正稳的版本往往多出一倍的防御性判断。










