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

TCP连接收到FIN包后为何仍返回EAGAIN而非EOF?

TCP连接关闭后syscall.Read返回EAGAIN而非EOF的问题

测试代码

1. TCP服务器初始化代码

type server struct {
    serverConns chan net.Conn
    addr        string
}

func (s *server) init() error {
    s.serverConns = make(chan net.Conn)

    l, err := net.Listen(network, ":8888")
    if err != nil {
        return err
    }
    s.addr = l.Addr().String()
    fmt.Println(s.addr)

    go func() {
        for {
            conn, err := l.Accept()
            if err != nil {
                panic(err)
            }
            s.serverConns <- conn
        }
    }()

    return nil
}

2. 测试用例代码

func TestRemoteEOF(t *testing.T) {
  var s server
  require.Nil(t, s.init())
  
  // dial to server
  var n net.Conn 
  
  // close connection from server 
  serverConn := <-s.serverConns
  require.Nil(t, serverConn.Close())

  buf := make([]byte, 100)
  hit := time.Now()
  err2 := checkConnErr(n, buf)
  require.Equal(t, err2, io.EOF)
}

问题描述

服务器主动关闭连接后,我已通过Wireshark确认hit时间点晚于FIN包到达客户端的时间,但调用封装了syscall.Read的checkConnErr函数时,返回的却是EAGAIN(表示连接仍存活),而非预期的io.EOF,请问该现象的原因是什么?

原因分析

  • 非阻塞IO模式的直接影响:如果客户端连接被设置为非阻塞模式,即使收到FIN包,当TCP接收缓冲区中没有数据时,syscall.Read会直接返回EAGAIN错误。内核此时仅告知当前无数据可读,并非直接标识连接关闭;只有后续再次调用syscall.Read时,才会返回0字节(对应EOF)。
  • 系统调用与标准库封装的差异:Go标准库的net.Conn.Read会自动处理非阻塞场景的EAGAIN(通过阻塞等待直到有数据或EOF),并将返回0字节的情况转为io.EOF。但直接调用syscall.Read会绕过这些封装,返回原始的系统调用错误,因此会出现你看到的EAGAIN。
  • TCP半关闭状态的特性:服务器发送FIN包后,连接进入半关闭状态(客户端仍可向服务器发送数据)。此时若客户端接收缓冲区为空,非阻塞的syscall.Read会返回EAGAIN,而非直接触发EOF标识。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 10:45:32