Search code examples
jsongotcptcplistenertcpserver

Go concurrent TCP server hangs when JSON is sent as the response but works with plain string


I am trying to implement a concurrent TCP server in Go and found this great explanatory article in linode where it clearly explains with a sample code. The sample code snippets for the client and server and included below.

Concurrent TCP server where for each TCP client a new go-routine is created.

package main

import (
        "bufio"
        "fmt"
        "net"
        "os"
        "strconv"
        "strings"
)

var count = 0

func handleConnection(c net.Conn) {
        fmt.Print(".")
        for {
                netData, err := bufio.NewReader(c).ReadString('\n')
                if err != nil {
                        fmt.Println(err)
                        return
                }

                temp := strings.TrimSpace(string(netData))
                if temp == "STOP" {
                        break
                }
                fmt.Println(temp)
                counter := strconv.Itoa(count) + "\n"
                c.Write([]byte(string(counter)))
        }
        c.Close()
}

func main() {
        arguments := os.Args
        if len(arguments) == 1 {
                fmt.Println("Please provide a port number!")
                return
        }

        PORT := ":" + arguments[1]
        l, err := net.Listen("tcp4", PORT)
        if err != nil {
                fmt.Println(err)
                return
        }
        defer l.Close()

        for {
                c, err := l.Accept()
                if err != nil {
                        fmt.Println(err)
                        return
                }
                go handleConnection(c)
                count++
        }
}

TCP client code snippet

package main

import (
        "bufio"
        "fmt"
        "net"
        "os"
        "strings"
)

func main() {
        arguments := os.Args
        if len(arguments) == 1 {
                fmt.Println("Please provide host:port.")
                return
        }

        CONNECT := arguments[1]
        c, err := net.Dial("tcp", CONNECT)
        if err != nil {
                fmt.Println(err)
                return
        }

        for {
                reader := bufio.NewReader(os.Stdin)
                fmt.Print(">> ")
                text, _ := reader.ReadString('\n')
                fmt.Fprintf(c, text+"\n")

                message, _ := bufio.NewReader(c).ReadString('\n')
                fmt.Print("->: " + message)
                if strings.TrimSpace(string(text)) == "STOP" {
                        fmt.Println("TCP client exiting...")
                        return
                }
        }
}

The above concurrent TCP server and client works without any issue. The issue comes when I change the TCP server to send a JSON response instead of the text response. When I change the line:

counter := strconv.Itoa(count) + "\n"
c.Write([]byte(string(counter)))

to

res, err := json.Marshal(IdentitySuccess{MessageType: "newidentity", Approved: "approved"})
if err != nil {
    fmt.Printf("Error: %v", err)
}
c.Write(res)

the server hangs and does not send any response back to client. The strange thing is that when I shut down the server forcefully with Ctrl+C, the server sends the response to the client. Any idea about this strange behavior? It's like the server holds the response and sends it when it exists.


Solution

  • That socket tutorial, just as so many other broken-by-design socket tutorials, doesn't explain at all what an application protocol is or why you need it. All it says is:

    In this example, you have implemented an unofficial protocol that is based on TCP.

    This "unofficial" protocol is as rudimentary as it gets: messages are separated by newline characters (\n).

    You should not be using sockets like that in any environment, apart from learning the basics about sockets.

    You need an application protocol to frame messages (so your client and server can recognise partial and concatenated messages).

    So the short answer: send a \n after your JSON. The long answer: don't use barebones sockets like this, use an application protocol, such as HTTP.