You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在关闭客户端连接后从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)
}

方案说明

  1. CloseWrite()只会关闭连接的写方向,服务器仍然可以从读方向读取数据,直到客户端关闭连接或发送完所有数据后触发EOF。
  2. 客户端收到FIN包后,后续的Write()会返回错误(broken pipe),从而停止发送,符合“告知客户端停止发送”的需求。
  3. 服务器会继续读取操作系统缓冲区中已接收的所有数据,直到scanner.Scan()返回false(EOF),这样就能拿到客户端已发送的全部内容。

额外注意:客户端需要确保在收到写错误后正确关闭连接,避免服务器一直等待EOF。

内容的提问来源于stack exchange,提问作者ray

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 11:04:54