0

I am learning golang and trying to write a simple server, but something wrong to get output from the server when testing.

Code

package main

import (
    log "log"
    net "net"
    _ "time"
)

func main() {
    listener, err := net.Listen("tcp", ":12345")
    if err != nil {
        log.Fatalln(err)
    }
    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Println(err)
        }
        go ClientHandler(conn)
    }
}

func ClientHandler(conn net.Conn) {
    defer func() {
        if v := recover(); v != nil {
            log.Println("Catch a panic:", v)
            log.Println("Prevent the program from crashing")
        }
        if err := conn.Close(); err != nil {
            log.Println(err)
        }
    }()

    b := make([]byte, 8)
    _, err := conn.Read(b)
    if err != nil {
        panic(err)
    }

    if _, err := conn.Write(append(b, '\n')); err != nil {
        panic(err)
    }
    // time.Sleep(time.Millisecond * time.Duration(100))
}

Test method

I tested it with netcat in bash.

function test_server {
    for i in `seq 1 $1`; do
        echo -n "$i: "
        echo "askjaslkfjlskflask" | nc localhost 12345
    done
}

When the data is more than or equal to the server side b's buffer size , the output will be messed up.

$ # TEST 1
$ test_server 10
1: 2: askjaslk
3: askjaslk
4: 5: askjaslk
6: askjaslk
7: askjaslk
8: 9: 10: %

if less than or uncomment the time.Sleep(), all will be ok

$ # TEST 2
1: askja
2: askja
3: askja
4: askja
5: askja
6: askja
7: askja
8: askja
9: askja
10: askja

What's more, when I debug the code, nothing seems wrong and the output will be like TEST 2.

Question

Now I realize that I should use a for loop or a larger buffer b to receive whole data. However, I can't understand why the output is messed up. I think there should be no data transfered or whole data transfered like TEST 2.

Update the Question

It's the openbsd netcat caused that. So, the question is what happens when using openbsd netcat.

Conclusion

Close when still has unread data causes that problem

neodrrr
  • 3
  • 3
  • The code in the question does not produce the output in the question. We can't help you fix code we don't see. Either edit your question by adding the relevant code or delete the question if you are not able to share the code. See [mcve]. – mkopriva Dec 08 '21 at 04:16
  • @mkopriva Something wrong with my description? Just **compile** the golang code, **run it as server** and then run **bash** fucntion `test_server` to **commicate with server**. Take a little time to watch it please. Thanks – neodrrr Dec 08 '21 at 04:35
  • @mkopriva Sorry, my bad. I forgot I modified it locally. I have corrected it now. Please have a look at it. Thanks – neodrrr Dec 08 '21 at 04:43
  • https://imgur.com/bo3VY6O -- I ran the updated code and the output looks fine to me (i've tried with the sleep commented out and uncommented) – mkopriva Dec 08 '21 at 04:54
  • @mkopriva [result](https://postimg.cc/vxF8Jw6w) do u run the test several times? My go version is go1.17.4 linux/amd64 – neodrrr Dec 08 '21 at 04:58
  • I ran it multiple times, the output is always correct. I'm using a bash script file to define-and-execute the `test_server` function (see the `cat` output in the imgur link above), you should try that too, instead of doing directly in the shell, it is very likely that this is a shell thing and has nothing to do with Go. (I'm using Go 1.17.3 darwin/amd64) – mkopriva Dec 08 '21 at 05:02
  • 1
    @mkopriva [Still messed output](https://postimg.cc/q6sKYLqQ) I will try to use other versions of go to test then. Thanks – neodrrr Dec 08 '21 at 05:13
  • 1
    @mkopriva I figured it out. I am using openbsd netcat in linux. When I switch to gnu netcat, everything goes right. So, the question is what happens when using openbsd version netcat. Thanks a lot – neodrrr Dec 08 '21 at 05:39

1 Answers1

0

What happens here is that the server does not read the full data from the client, then closes the connection. Closing a TCP connection with data still in the read buffer will cause the server to send a connection reset - which can also be seen as RST when doing a packet capture.

If the RST arrives at about the same time as the response from the server (the conn.Write) it might lead to the RST processed first, i.e. the response will not be processed but the connection will be considered closed. So here is a race condition which means that sometimes output will happen (response processed first) or not (RST processed first).

The time.Sleep changes the time between sending the response and sending the RST, so that there is enough time to handle the response by netcat before handling the later RST - which means guaranteed output.

Steffen Ullrich
  • 114,247
  • 10
  • 131
  • 172
  • Thanks. The explanation almost fits the phenomenon. But no matter how large the buffer size is, b should get data without error and then run to `Write` and `Close`. So how does the buffer size affect the output? In other words, how does `Read` affect the behavior of `Write` and `Close`? – neodrrr Dec 08 '21 at 09:01
  • Maybe `Read` doesn't affect the behavior of `Write` and `Close`, but different version of client netcat have different behavior to process data and RST received concurrently. Openbsd version may close connection and drop received data without any output. – neodrrr Dec 08 '21 at 09:30
  • @neodrrr: Read doesn't directly affect the behavior of Close, but unread data on Close cause the RST. This is also a problem not only seen with netcat, but also browsers when the server does not read the full request - see for example https://stackoverflow.com/questions/54823923/http-client-receives-no-response-when-server-reads-only-headers-from-request or https://stackoverflow.com/questions/24852821/perl-connection-reset-with-simple-http-server – Steffen Ullrich Dec 08 '21 at 09:51