【问题标题】:How to know TCP connection is closed in net package?如何知道 TCP 连接在 net 包中关闭?
【发布时间】:2012-09-26 07:44:36
【问题描述】:

我正在实现一个小型 TCP 服务器。我如何知道我的一位客户是否已关闭?我应该尝试读取还是写入并检查err 是否为零?

【问题讨论】:

  • 你在error附近看过吗?

标签: tcp go


【解决方案1】:

该线程“Best way to reliably detect that a TCP connection is closed”,使用net.Conn 表示“c”(也见于utils/ping.golocale-backend/server.gomany other instances):

one := make([]byte, 1)
c.SetReadDeadline(time.Now())
if _, err := c.Read(one); err == io.EOF {
  l.Printf(logger.LevelDebug, "%s detected closed LAN connection", id)
  c.Close()
  c = nil
} else {
  var zero time.Time
  c.SetReadDeadline(time.Now().Add(10 * time.Millisecond))
}

对于检测超时,建议:

if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
  ...

2019 年更新:tuxedo25 提及 in the comments

在 go 1.7+ 中,零字节读取立即返回并且永远不会返回错误。
您必须至少读取一个字节。

commit 5bcdd63go issue 15735

net:不要从零字节读取返回 io.EOF

【讨论】:

  • 变量one是什么?大小为零的切片是有意义的。
  • 使用大小为零的切片怎么样?然后什么都不会被读取,我们只会检查超时。 zero := make([]byte,0); ... c.Read(zero); ...
  • @VonC 我有一个问题:c.SetReadDeadline(time.Now()) 是否意味着会立即触发超时? c.SetReadDeadline(time.Time{}) 怎么样?这是否意味着永远不会触发超时?
  • 在这个方法中,如果读取的字节,我要求它放回去。这似乎是不可能的。
  • 我认为这实际上行不通。我已经尝试了这个答案提出的所有可能的组合,它永远不会检测到关闭的连接。我知道这一点,b/c 我有这段代码,然后程序继续对连接和后续的 Read() 执行 Write(),由于连接有错误,因此失败并出现 ErrUnexpectedEOF 错误已关闭。
【解决方案2】:

尝试读取它,如果它被关闭它会抛出一个错误。如果您愿意,请优雅地处理!

对于放弃过多的风险:

func Read(c *net.Conn, buffer []byte) bool {
    bytesRead, err := c.Read(buffer)
    if err != nil {
        c.Close()
        log.Println(err)
        return false
    }
    log.Println("Read ", bytesRead, " bytes")
    return true
}

这里有一个很好的介绍使用net包制作一个小型TCP“聊天服务器”:

Golang Away: TCP Chat Server

【讨论】:

  • 有很多可能的错误。只有其中一个表示连接已被对等方关闭。
【解决方案3】:

在这方面挣扎了一段时间后,这是一个 POSIX 解决方案,它使用MSG_PEEK 来防止耗尽缓冲区并导致竞争条件。这使您可以检查 TCP 套接字的 READ 部分是否仍然从另一个 goroutine 打开:

func connCheck(conn net.Conn) error {
    var sysErr error = nil
    rc, err := conn.(syscall.Conn).SyscallConn()
    if err != nil { return err }
    err = rc.Read(func(fd uintptr) bool {
        var buf []byte = []byte{0}
        n, _, err := syscall.Recvfrom(int(fd), buf, syscall.MSG_PEEK | syscall.MSG_DONTWAIT)
        switch {
        case n == 0 && err == nil:
            sysErr = io.EOF
        case err == syscall.EAGAIN || err == syscall.EWOULDBLOCK:
            sysErr = nil
        default:
            sysErr = err
        }
        return true
    })
    if err != nil { return err }

    return sysErr
}

这是基于上面的 mysql#connCheck,但它执行 1 字节读取系统调用,这可能与尝试读取流的其他 goroutine 发生冲突。

【讨论】:

  • 请注意,如果另一个 goroutine 没有主动读取()流,这将不会返回 EOF,直到流被读取并耗尽其缓冲区。
【解决方案4】:

2019 年 4 月状态:

阅读https://github.com/golang/go/issues/15735上的主题和帖子

如果服务器没有在连接上写入(读取会失败,但写入不会),则普遍缺乏官方支持来检测服务器是否关闭了 TCP 连接。

https://github.com/methane 提供了一个解决方案,该解决方案仅适用于 Linux,而且价格昂贵,因为它会进行一些分配 -

这里有:https://github.com/go-sql-driver/mysql/blob/master/conncheck.go

我发现这是可行的,但不是跨平台阻止了我采用它 -

【讨论】:

    【解决方案5】:

    我的五分钱,我认为最好写到连接并得到一个错误,感谢上面的答案,这是我的工作解决方案

    func isTCPWorking(c net.Conn) bool {
       _, err := c.Write([]byte("OK"))
       if err != nil {
          c.Close() // close if problem
          return false
       }
       return true
    }
    

    【讨论】:

      【解决方案6】:
              _, err := conn.Read(make([]byte, 0))
              if err!=io.EOF{
                  // this connection is invalid
                  logger.W("conn closed....",err)
      
              }else{
                  byt, _:= ioutil.ReadAll(conn);
              }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-20
        • 2023-03-25
        相关资源
        最近更新 更多