net.listener 不能直接读写消息,因其仅负责接受连接并返回 net.conn 实例,收发数据必须通过该 conn 操作;accept() 阻塞返回新 conn,需为每个 conn 单独启 goroutine 处理,且 read/write 是无边界的字节流,需自行处理粘包与协议。

为什么 net.Listener 不能直接读写消息?
因为 net.Listener 只负责接受连接,返回的是 net.Conn 实例,真正收发数据必须操作这个连接对象。很多人卡在“监听了却收不到消息”,其实是误以为 listener.Accept() 后就能用 listener 读——不行,得把返回的 conn 拿去处理。
-
listener.Accept()是阻塞调用,每次返回一个新net.Conn,代表一个客户端连接 - 每个
conn需要单独启 goroutine 处理,否则后续连接会被阻塞 -
conn.Read()和conn.Write()是字节流操作,没有内置消息边界,不加协议会粘包 - 别用
fmt.Scanln或bufio.NewReader(os.Stdin)去读 conn——那是读控制台的
怎么避免多个客户端之间消息互相覆盖?
Go 的 net.Conn 本身是线程安全的读写,但如果你用全局变量(比如 map 存所有 conn)又没加锁,或者多个 goroutine 并发往同一个 conn.Write() 写,就会乱序甚至 panic。
- 每个客户端连接建议封装成结构体,包含
*net.Conn、username、id等字段 - 广播消息时,遍历 clients map 要加
sync.RWMutex读锁;增删 client 要写锁 - 不要在 goroutine 里直接
conn.Write([]byte(msg))—— 改用带缓冲的 channel + 单独 writer goroutine,防止 write 阻塞整个 handler - 如果用
bufio.Scanner读,记得设置Split(bufio.ScanLines),否则默认按空格切分,中文或特殊字符会出错
conn.Read() 返回 0 字节或 io.EOF 怎么判断断连?
conn.Read() 返回 n == 0 不代表错误,只表示暂时没数据;真正断开是返回 n == 0 && err == io.EOF,或者 err != nil && !errors.Is(err, net.ErrClosed)。
- 别一看到
n == 0就 close conn——可能是客户端发了个空行,或 TCP keepalive 探测包 - 典型误判:用
if n == 0 { break }结束循环,结果客户端 Ctrl+D 或网络闪断时没清理资源 - 正确做法是检查
err:只有errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed)才算正常下线 - 非 EOF 的
err(如read: connection reset by peer)要记录日志,并主动 close conn
为什么本地测试通,一上服务器就丢消息或连不上?
常见不是代码问题,而是环境配置和连接模型没对齐:Linux 默认限制单机最大连接数、防火墙拦截、NAT 超时、客户端未设超时导致连接堆积。
立即学习“go语言免费学习笔记(深入)”;
- 服务器启动后检查端口是否真在监听:
ss -tlnp | grep :8080,别只信 log 里的 “server started” - 如果用云服务器,安全组必须放行对应端口(TCP),且协议选 TCP,不是 “全部”
- 客户端 connect 时建议设
net.DialTimeout,服务端listener.SetDeadline避免半开连接堆积 - 大量并发时,
net.Listen("tcp", ":8080")默认用系统 backlog,可显式调大:net.ListenConfig{KeepAlive: 30 * time.Second}
实际跑起来最常被忽略的,是连接关闭时没从 clients map 中移除,也没关掉对应的 reader/writer goroutine,导致内存缓慢上涨、fd 耗尽。这不是语法问题,是状态管理没闭环。










