有时候客户端无法从服务器获取输出。

huangapple go评论89阅读模式
英文:

Can't get output from server in client sometime

问题

我正在学习golang,并尝试编写一个简单的服务器,但在测试时无法从服务器获取输出。

代码

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("捕获到 panic:", v)
			log.Println("防止程序崩溃")
		}
		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))
}

测试方法

我在bash中使用netcat进行测试。

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

当数据大于或等于服务器端的b缓冲区大小时,输出会混乱。

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

如果数据量较小或取消注释time.Sleep(),则一切正常。

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

此外,当我调试代码时,似乎没有问题,输出结果会像测试2一样。

问题

现在我意识到我应该使用for循环或更大的缓冲区b来接收完整的数据。然而,我不明白为什么输出会混乱。我认为应该没有数据传输或者像测试2一样传输完整的数据。

更新问题

是OpenBSD的netcat引起了这个问题。所以,问题是使用OpenBSD的netcat时会发生什么。

结论

在仍有未读数据时调用Close会导致这个问题。

英文:

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

答案1

得分: 0

这里发生的情况是服务器没有完全读取客户端的数据,然后关闭了连接。在读取缓冲区中仍有数据的情况下关闭TCP连接会导致服务器发送连接重置,也可以在进行数据包捕获时看到为RST。

如果RST与服务器的响应(conn.Write)几乎同时到达,可能会导致RST先被处理,即响应不会被处理,但连接将被视为关闭。因此,这里存在竞争条件,有时会发生输出(响应先被处理)或不发生输出(RST先被处理)。

time.Sleep 改变了发送响应和发送RST之间的时间,以便在处理后续的RST之前有足够的时间通过netcat处理响应,这意味着有保证的输出。

英文:

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.

huangapple
  • 本文由 发表于 2021年12月8日 11:51:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/70269728.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定