Search code examples
gotcp

How to fully read buffered content from a tcp socket after closing the client connection?


I have a Go server listening on a tcp socket and during shutdown I want it to tell the client to stop sending more data, but also read everything the client has sent thus far. What I see is that once the client connection is closed, the server stops reading, but it never receives everything the client thinks it sent. I think what is happening is that the OS is buffering tcp packets received and then dropping them when the server closes the client connection.

Here is a program that shows the issue. The server listens, then prints out what it received; the client sends and prints out what it sent. The server interrupts the client to stop it, but at the end I want these two lists to be the same.

  • This example uses bufio.Scanner to read the socket but I have also tried just reading bytes from the Connection, but get the same result.

  • conn.(*net.TCPConn).CloseWrite() Sounds like exactly what I want, but that didn't seem to interrupt the client at all.

  • conn.(*net.TCPConn).SetReadBuffer(0) - also tried this, but 0 is not an allowed value (panic).

package main

import (
    "bufio"
    "fmt"
    "net"
    "strconv"
    "sync"
    "time"
)

const port = ":8888"

var wg sync.WaitGroup

func main() {
    wg.Add(1)
    go func() {
        // wait for the server to start
        time.Sleep(1000 * time.Millisecond)
        client()
    }()

    // server listens
    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())

        // arbitrary condition: simulate a server shutdown - interrupt the client
        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)
            // slow down the sends to make output readable
            //time.Sleep(1 * time.Millisecond)
        }
    }
    fmt.Println("Client sent: ", sent)
}

The output is something like this on my machine:

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]

Solution

  • TCP provides just a reliable bidirectional byte stream without any inherent semantics. If the server wants the client to signal that it should not longer send any data, then this functionality has to be implemented as part of the application protocol, i.e. a layer above the pure data transport provided by TCP.