如何在关闭客户端连接后从TCP Socket读取全部缓冲内容?
Go TCP服务器停机时无法接收客户端已发送全部数据的问题解决
问题描述
我有一个基于Go语言的TCP Socket监听服务器,在停机(shutdown)时,希望告知客户端停止发送更多数据,同时读取客户端迄今为止已发送的所有内容。但发现一旦客户端连接被关闭,服务器就会停止读取,且永远无法接收到客户端认为已发送的全部数据。怀疑是操作系统缓冲了已接收的TCP数据包,在服务器关闭客户端连接时将其丢弃了。
复现场景:服务器监听并打印接收到的内容;客户端发送数据并打印已发送的内容。服务器会在接收2条数据后中断客户端以模拟停机,但最终两端的内容列表无法完全一致。
已尝试的无效方案:
- 使用
bufio.Scanner或直接从Connection读取字节,结果相同 conn.(*net.TCPConn).CloseWrite()无法中断客户端conn.(*net.TCPConn).SetReadBuffer(0)会触发panic(0不是合法值)
复现代码
package main import ( "bufio" "fmt" "net" "strconv" "sync" "time" ) const port = ":8888" var wg sync.WaitGroup func main() { wg.Add(1) go func() { // 等待服务器启动 time.Sleep(1000 * time.Millisecond) client() }() // 服务器监听 wg.Add(1) server() wg.Wait() } func server() { defer wg.Done() listener, err := net.Listen("tcp", port) if err != nil { panic(err) } received := make([]string, 0) conn, err := listener.Accept() if err != nil { panic(err) } defer conn.Close() scanner := bufio.NewScanner(conn) for i := 0; scanner.Scan(); i++ { received = append(received, scanner.Text()) // 模拟服务器停机:中断客户端 if len(received) == 2 { _ = conn.(*net.TCPConn).Close() } } fmt.Println("Server received: ", received) } func client() { defer wg.Done() conn, err := net.Dial("tcp", port) if err != nil { panic(err) } sent := make([]string, 0) defer conn.Close() for i := 0; i < 50000; i++ { v := strconv.Itoa(i) _, err := conn.Write([]byte(v + "\n")) if err != nil { fmt.Println("Client interrupted:", err) break } else { sent = append(sent, v) // 减慢发送速度以方便查看输出 //time.Sleep(1 * time.Millisecond) } } fmt.Println("Client sent: ", sent) }
运行输出
Server received: [0 1 2] Client interrupted: write tcp 127.0.0.1:49274->127.0.0.1:8888: write: broken pipe Client sent: [0 1 2 3 4 5 6 7 8 9 10 11 12 13]
解决方案
问题核心在于:直接调用Close()会同时关闭读写两端,导致服务器无法继续读取操作系统缓冲区中已收到但未处理的数据。正确的做法是先关闭写端(告知客户端停止发送),然后继续读取直到连接EOF,最后再关闭读端。
修改后的服务器代码
func server() { defer wg.Done() listener, err := net.Listen("tcp", port) if err != nil { panic(err) } received := make([]string, 0) conn, err := listener.Accept() if err != nil { panic(err) } tcpConn := conn.(*net.TCPConn) defer tcpConn.Close() scanner := bufio.NewScanner(conn) for i := 0; scanner.Scan(); i++ { received = append(received, scanner.Text()) // 模拟服务器停机:关闭写端,告知客户端停止发送 if len(received) == 2 { // 关闭写端,发送FIN包,客户端后续写操作会报错 _ = tcpConn.CloseWrite() } } fmt.Println("Server received: ", received) }
方案说明
CloseWrite()只会关闭连接的写方向,服务器仍然可以从读方向读取数据,直到客户端关闭连接或发送完所有数据后触发EOF。- 客户端收到FIN包后,后续的
Write()会返回错误(broken pipe),从而停止发送,符合“告知客户端停止发送”的需求。 - 服务器会继续读取操作系统缓冲区中已接收的所有数据,直到
scanner.Scan()返回false(EOF),这样就能拿到客户端已发送的全部内容。
额外注意:客户端需要确保在收到写错误后正确关闭连接,避免服务器一直等待EOF。
内容的提问来源于stack exchange,提问作者ray
相关产品推荐
相关产品推荐

