
本文讲解如何在 Go 中实现 TCP 服务端持续接收客户端发送的多条独立消息,无需每次通信都关闭连接;核心方案是采用基于换行符(\n)的消息边界协议,并配合 bufio.Scanner 或 ReadString 进行安全、高效的消息拆分。
本文讲解如何在 go 中实现 tcp 服务端持续接收客户端发送的多条独立消息,无需每次通信都关闭连接;核心方案是采用基于换行符(`\n`)的消息边界协议,并配合 `bufio.scanner` 或 `readstring` 进行安全、高效的消息拆分。
在 TCP 协议中,数据以字节流形式传输,本身不携带消息边界信息。原始代码使用 io.Copy 将整个连接数据一次性读取到缓冲区,这导致服务端必须等待连接关闭才能“看到”完整内容——显然无法支持多消息复用单连接的场景。
解决的关键在于定义并识别消息边界。最常用、轻量且兼容性好的方式是采用 行分隔协议(Line-based Protocol):每条消息以 \n(或 \r\n)结尾。Go 标准库提供了开箱即用的支持工具:
✅ 推荐实现:使用 bufio.Scanner(更健壮)
相比 ReadString,bufio.Scanner 内置缓冲管理、自动处理换行、支持超时与最大长度限制,更适合生产环境:
go func(c net.Conn) {
defer c.Close()
scanner := bufio.NewScanner(c)
scanner.Split(bufio.ScanLines) // 显式指定按行分割(默认即为此)
for scanner.Scan() {
msg := scanner.Text() // 自动去除换行符
log.Printf("Received message (%d bytes): %s", len(msg), msg)
// ✅ 可在此处解析 JSON、路由请求、写入日志等
}
if err := scanner.Err(); err != nil && err != io.EOF {
log.Printf("Scan error: %v", err)
}
}(conn)
⚠️ 注意事项与最佳实践
- 客户端必须严格发送换行符:Node.js 客户端需确保每条 JSON.stringify(packet) 后调用 client.write('\n'),否则服务端将永久阻塞等待 \n。
- 避免 ReadString('\n') 的潜在陷阱:若消息过大或网络延迟导致单次读取未包含 \n,ReadString 会阻塞直至超时或连接关闭;而 Scanner 可通过 scanner.MaxScanTokenSize() 设定单行最大长度,防止内存溢出。
- 连接生命周期管理:服务端 goroutine 应在 defer c.Close() 确保资源释放;客户端发送完毕后可调用 client.end() 主动关闭写端(保持读端接收响应),而非直接 destroy()。
- 编码一致性:双方均需明确使用 UTF-8 编码,JSON 字符串天然符合该要求,无需额外转换。
? 完整可运行服务端示例
package main
import (
"bufio"
"log"
"net"
)
func main() {
l, err := net.Listen("tcp", ":1200")
if err != nil {
log.Fatal("Listen failed:", err)
}
defer l.Close()
log.Println("Server listening on :1200")
for {
conn, err := l.Accept()
if err != nil {
log.Printf("Accept error: %v", err)
continue
}
go func(c net.Conn) {
defer c.Close()
scanner := bufio.NewScanner(c)
scanner.Buffer(make([]byte, 4096), 1<p>通过此设计,单个 TCP 连接即可承载任意数量的结构化消息(如 JSON),兼顾简洁性与可靠性,是构建轻量级 RPC、日志上报或 IoT 设备通信服务的典型范式。</p>










