Search code examples
gotcp

TCP client or server stucks at processing data


I'm trying to write a simple tcp server and client program.

When I run both the server and client code below, the client will simply receive the time message from server and exit, and the server continues to accept a new connection. My expected program behaviour is I want the server to also receive the "hello world" message from the client and and close the connection and so does client by using the "responseConnection" function.

But the problem is when I started the client, the server seems to be stuck at the conn.Read function inside "responseConnection" function and has error "EOF exit status 1" when I stopped client first, which is really ambiguous to debug. When I stopped server first, client will receive the time data from server.

If you have any ideas why, please help to answer as I'm totally a newbie to Golang. Thank you so much!

server.go

package main

import (
    "fmt"
    "io"
    "log"
    "net"
    "time"
)

type connection struct {
    host    string
    port    string
    network string
}

func checkError(err error) {
    if err != nil {
        log.Fatalln(err)
    }

}

func responseConnection(conn net.Conn) {
    defer conn.Close() // <-- When responseConnection() is used, add this
    buf := make([]byte, 0, 4096)
    tmp := make([]byte, 256)

    for {
        n, err := conn.Read(tmp)
        if err != nil {
            checkError(err)
            if err != io.EOF {
                fmt.Println("Error reading: ", err)
            }
            // fmt.Println(err)
            break
        }
        buf = append(buf, tmp[:n]...)
    }
    fmt.Println("Data from client ========> ", string(buf))
    // c <- buf

}

func handleConnection(conn net.Conn) {
    defer conn.Close() // <- When responseConnection() is used, remove this
    dayTime := time.Now().String()

    conn.Write([]byte(dayTime))
    // responseConnection(conn)
    fmt.Println("The end of handleConnection")
}

func main() {
    localConn := connection{
        host:    "",
        port:    "5555",
        network: "tcp",
    }
    // servicePort := ":5555"
    tcpAddr, err := net.ResolveTCPAddr(localConn.network, ":"+localConn.port)
    checkError(err)
    l, err := net.ListenTCP("tcp", tcpAddr)
    fmt.Println("Server starts listening on port", localConn.port)
    checkError(err)
    
    for {
        fmt.Println("Accepting a new connection....")
        conn, err := l.Accept()
        checkError(err)
        handleConnection(conn)
        // responseConnection(conn)
    }

}

client.go

package main

import (
    "bytes"
    "fmt"
    "io"
    "log"
    "net"
    "os"
)

func checkError(err error) {
    if err != nil {
        log.Fatalln(err)
    }
}

var buf bytes.Buffer

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0])
        os.Exit(1)
    }
    service := os.Args[1]
    
    tcpAddr, err := net.ResolveTCPAddr("tcp", service)
    checkError(err)
    

    conn, err := net.DialTCP("tcp", nil, tcpAddr)
    checkError(err)
    
    message := "Hello world from client"
    conn.Write([]byte(message))

    
    io.Copy(&buf, conn)
    buf.Cap()
    fmt.Printf("Data from server =======>: %s\n Buffer length: %d\n", buf.String(), buf.Len())

    defer conn.Close()
}


Solution

  • I think your problem is quite the standard one: not paying attention to the fact TCP does not implement message boundaries and merely transports two opaque streams of bytes over a connection.

    This means, when you send a string of bytes "Hello world from client" to a connected socket (an established TCP connection) the other end of the connection have no idea where the client's message ends unless the client somehow conveys that itself; there simply are no means to demarcate individual messages using TCP itself.

    Enter "application-level protocols": unless the data exchange task for which you intend to use TCP naturally transfer a single "message" — imagine a server which dumps the contents of a single file into each connected client and closes the connection — you have to invent some way for the client to tell the server where each of the messages it sends actually ends.

    Consider your example: in the reading procedure you basically have a loop wihch repeatedly reads chunks of data from a socket, with a single exit condition: reaching end-of-file on that socket. A EOF is only reported when the remote side — the client in our case — closes its side of the conection, and the client never does that: it sends a string and then waits for the sever to send something back, but the server never replies because it never finishes reading.

    There are multiple ways to solve the problem.

    • Invent a custom protocol (say, in the TLV family) which would implement message framing.

      Say, in its simplest form, the protocol could be defined as a single unsigned byte containing the length of the following message, in bytes.
      The server would then have a two-step process for reading each of the client's messages:

      1. Read a single byte;
      2. If successful, read as many following bytes as defined by the value of the leading byte;
      3. Once successful, go back to step 1 to read the following message.
    • Come up with a message delimiter such as ASCII LF character — wich can be encoded as \n in Go's string literals, ­— and make the server continue reading until it encounters an LF; once it has spotted an LF, it knows it should process the message then start reading another.

      Go has a convenient type bufio.Reader right in its standard package which can read from any io.Reader individual lines separated by LFs.

    • Have a more involved message framing such as sending JSON documents using JSON streaming.

      An oft-overseen feature of the decoder implemented by the stock encoding/json package is that it's fine with decoding streams of JSON objects, consider this as an example.

    The possibilities are actually many, so I were just scratching the surface, and I think you should get the idea.