Home > Back-end >  How do I make it such that a TCP connection will timeout if the connection doesn't receive a re
How do I make it such that a TCP connection will timeout if the connection doesn't receive a re

Time:04-25

I'm trying to create a TCP server that will timeout if the client does not respond within the span of every second.

I tried:

func main() {
    listener, err := net.Listen("tcp", "localhost:8000")
    if err != nil {
        log.Fatal(err)
    }
    for {
        conn, err := listener.Accept()
        conn.SetDeadline(time.Now().Add(timeout))
        if err != nil {
            log.Print(err)
        }
        go handleConn(conn)
    }

}

where the timeout is a single second but the disconnects immediately, not even waiting for a reply.

CodePudding user response:

What you want can be achieved by setting socket options on your listener. Tweak the values as per your needs

Note that this is its own KeepAlive and does not depend on incoming/outgoing data by application

func enableTCPKeepAlive(listener *net.TCPListener) error {
    rawConn, err := listener.SyscallConn()
    if err != nil {
        return err
    }
    cfg := config.TLSServerConfig()
    rawConn.Control(
        func(fdPtr uintptr) {
            // got socket file descriptor. Setting parameters.
            fd := int(fdPtr)
            //Idle time before sending probe.
            err = syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, cfg.TCPKAIdleTime)
            if err != nil {
                return err
            }
            //Number of probes.
            err := syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPCNT, cfg.TCPKANumProbes)
            if err != nil {
                return err
            }
            //Wait time after an unsuccessful probe.
            err = syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, cfg.TCPKAInterval)
            if err != nil {
                return err
            }
            // go syscall doesn't have the constant 0x12 (18) for TCP_USER_TIMEOUT.
            // 0x12 value referenced from linux kernel source code header:
            // include/uapi/linux/tcp.h
            err = syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, 0x12, cfg.TCPKAUserTimeout)
            if err != nil {
                return err
            }
        })
    return nil
}

There are more options available than the ones I have mentioned above. Call this function on your listener before the for loop.

func main() {
    listener, err := net.Listen("tcp", "localhost:8000")
    if err != nil {
        log.Fatal(err)
    }
    err = enableTCPKeepAlive(listener)
    if err != nil {
        log.Fatal(err)
    }
    for {
        conn, err := listener.Accept()
        conn.SetDeadline(time.Now().Add(timeout))
        if err != nil {
            log.Print(err)
        }
        go handleConn(conn)
    }

}

CodePudding user response:

The problem is almost always in code that is not posted here. The function obviously works like a charme:

package main

import (
    "crypto/rand"
    "log"
    "net"
    "time"
)

func main() {
    listener, err := net.Listen("tcp", "localhost:8000")
    if err != nil {
        log.Fatal(err)
    }
    go func() {
        for {
            conn, err := listener.Accept()
            if err != nil {
                log.Print(err)
                return
            }
            go func(c net.Conn) {
                defer c.Close()

                conn.SetDeadline(time.Now().Add(2 * time.Second))
                if err != nil {
                    log.Print(err)
                    return
                }
                buf := make([]byte, 1<<19) // 512 KB
                for {
                    _, err := conn.Read(buf)
                    if err != nil {
                        log.Print(err)
                        break
                    }
                }
            }(conn)
        }
    }()

payload := make([]byte, 1<<20)
_, err = rand.Read(payload) // generate a random payload
if err != nil {
    log.Print(err)
}

conn, err := net.Dial("tcp", listener.Addr().String())
if err != nil {
    log.Fatal(err)
}
log.Println("Connected to server.")

time.Sleep(5 * time.Second)

_, err = conn.Write(payload)
if err != nil {
    log.Print(err)
}

listener.Close()
}
  • Related