net.Listen("tcp", ":8080") 是唯一正确的起点;必须显式指定网络类型如 "tcp"、"tcp4" 或 "tcp6",不可省略;":8080" 监听所有网卡,"127.0.0.1:8080" 限本地调试,"tcp4" 可规避双栈问题。

net.Listen("tcp", ":8080") 是唯一正确的起点
监听 TCP 端口必须显式指定网络类型为 "tcp"(或 "tcp4"/"tcp6"),不能只写 ":8080" 或 "localhost:8080" —— 否则会 panic:listen tcp: missing port in address 或 unknown network。冒号前留空(如 ":8080")表示监听所有可用网卡(等价于 "0.0.0.0:8080"),适合部署;若仅本地调试,建议用 "127.0.0.1:8080" 避免意外暴露。
-
net.Listen("tcp4", "127.0.0.1:8080")更明确:强制 IPv4 + 仅回环,规避双栈绑定失败问题 - 端口
- 端口被占用时错误是
bind: address already in use,可先用lsof -i :8080或netstat -tuln | grep 8080检查
listener.Accept() 必须配 goroutine,否则服务立即卡死
listener.Accept() 是阻塞调用,每次只返回一个 net.Conn;如果在主 goroutine 里直接处理(比如调用 conn.Read()),后续所有新连接都会排队等待——不是“慢”,而是彻底无法接入。这是新手最常踩的坑,现象是:第一个 telnet 连上了,第二个连不上,netstat 显示大量 SYN_RECV。
- 必须写成
go handleConnection(conn),让每个连接在独立 goroutine 中运行 -
handleConnection函数开头务必defer conn.Close(),否则连接泄漏,goroutine 积压后 OOM - 不要在循环里
break或return错误,应continue:监听器挂了才停,单个 Accept 失败不该终止整个服务
conn.Read() 不是一次读完一条消息,TCP 无边界是默认事实
TCP 是字节流,不是数据包。客户端调用三次 Write(),服务端一次 Read() 可能拿到全部拼接内容;也可能第一次只读到一半,第二次才收完——这叫“粘包”和“半包”。conn.Read(buf) 返回的是实际读到的字节数 n,不是 buf 长度。
- 永远检查
err == io.EOF:这是客户端正常断开的信号,不是错误,应 clean exit - 避免假设
n > 0就有有效数据;n == 0虽少见,但可能表示对端静默关闭 - 简单协议推荐
bufio.Scanner(按行)或bufio.NewReader(带缓冲),比裸Read()更可靠 - 生产环境务必设读写超时:
conn.SetReadDeadline(),否则恶意客户端发半包会导致 goroutine 永久阻塞
监听器关闭 ≠ 连接自动结束,优雅退出需手动管理
调用 listener.Close() 只是让 Accept() 不再返回新连接,已建立的 conn 仍活跃。若此时进程直接退出,正在处理的连接会被强制中断,客户端可能收不到最后响应。
立即学习“go语言免费学习笔记(深入)”;
- 没有内置“等待所有连接结束”的机制;常见做法是用
sync.WaitGroup计数活跃 goroutine - 更实用的是结合
context.WithTimeout控制 Accept 循环,并在收到 SIGINT/SIGTERM 后关闭 listener,再 sleep 几秒让现有连接自然完成 - 不要依赖
defer listener.Close()做优雅关闭——它只在 main 函数退出时触发,而你往往需要提前关
真正难的从来不是写通第一行 net.Listen,而是想清楚:连接谁来关、超时怎么设、消息怎么分界、异常怎么归类。这些细节不写进代码里,上线后只会以连接堆积、CPU 爆高、日志满屏 EOF 的方式提醒你。










