How to know TCP connection is closed in net package?

Solution 1:

That thread "Best way to reliably detect that a TCP connection is closed", using net.Conn for 'c' (also seen in utils/ping.go or locale-backend/server.go or many 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))
}

For detecting a timeout, it suggests:

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

Update 2019: tuxedo25 mentions in the comments:

In go 1.7+, zero byte reads return immediately and will never return an error.
You must read at least one byte.

See commit 5bcdd63 and go issue 15735

net: don't return io.EOF from zero byte reads

Solution 2:

Just try to read from it, and it will throw an error if it's closed. Handle gracefully if you wish!

For risk of giving away too much:

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
}

Here is a nice introduction to using the net package to make a small TCP "chat server":

"Golang Away: TCP Chat Server"

Solution 3:

After struggling for a while on this, here is a POSIX solution that uses MSG_PEEK to prevent draining the buffer and causing race conditions. This lets you check the whether the READ half of a TCP socket is still open from another 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
}

This is based on the above mysql#connCheck, but that one does a 1-byte read syscall, which can potentially conflict with other goroutines attempting to read a stream.

Solution 4:

April 2019 status:

Reading the threads and posts on https://github.com/golang/go/issues/15735

There is a general lack of official support to detect whether a server closed a TCP connection if the server does not write on the connection (reading will fail, but writing won't).

There is a solution provided by https://github.com/methane that works only on Linux and is expensive as it does some allocations -

This is available here: https://github.com/go-sql-driver/mysql/blob/master/conncheck.go

I found this to be working but not being cross platform is blocking me from adopting it -