TCP套接字:消息从先前的连接接收消息的部分。

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

TCP socket: message receives parts of a message from previous connection

问题

我在之前的连接中意外发现了一个错误,导致消息的一部分被传递到下一条消息中。

我有一个基本的服务器和客户端。为了避免示例代码过于臃肿,我删除了所有的错误处理。

此外,我用time.Sleep替换了一些Printf,因为我没有机会及时断开连接以重现错误,因为它读取数据的速度太快了。

这个"package"是一个简单的结构,前4个字节是长度,然后是内容。

客户端代码:

package main

import (
	"encoding/binary"
	"fmt"
	"net"
)

func main() {
	conn, _ := net.Dial("tcp", "0.0.0.0:8081")
	defer conn.Close()

	str := "msadsakdjsajdklsajdklsajdk"

	// 创建一个package
	buf := make([]byte, len(str)+4)
	copy(buf[4:], str)
	binary.LittleEndian.PutUint32(buf[:4], uint32(len(str)))

	for {
		_, err := conn.Write(buf)
		if err != nil {
			fmt.Println(err)
			return
		}
	}
}

服务器代码:

package main

import (
	"encoding/binary"
	"fmt"
	"net"
	"sync"
	"time"
)

func ReadConnection(conn net.Conn, buf []byte) (err error) {
	maxLen := cap(buf)
	readSize := 0
	for readSize < maxLen {
		// 替换Printf
		time.Sleep(time.Nanosecond * 10)

		readN, err := conn.Read(buf[readSize:])
		if err != nil {
			return err
		}
		readSize += readN
	}
	return nil
}

func handleConnection(conn net.Conn, waitGroup *sync.WaitGroup) {
	waitGroup.Add(1)
	defer conn.Close()
	defer waitGroup.Done()

	fmt.Printf("Serving %s\n", conn.RemoteAddr().String())

	var packageSize int32 = 0
	int32Buf := make([]byte, 4)

	for {
		// 读取长度
		conn.Read(int32Buf)
		packageSize = int32(binary.LittleEndian.Uint32(int32Buf))

		// 假设长度应为26
		if packageSize > 26 {
			fmt.Println("Package size error")
			return
		}

		// 读取内容
		packageBuf := make([]byte, packageSize)
		if err := ReadConnection(conn, packageBuf); err != nil {
			fmt.Printf("ERR: %s\n", err)
			return
		}

		// 替换Printf
		time.Sleep(time.Nanosecond * 100)
	}
}

func main() {
	// 建立连接
	listener, _ := net.Listen("tcp", "0.0.0.0:8081")
	defer listener.Close()

	waitGroup := sync.WaitGroup{}
	for {
		conn, err := listener.Accept()
		if err != nil {
			break
		}

		go handleConnection(conn, &waitGroup)
	}

	waitGroup.Wait()
}

因此,由于某种原因,int32Buf接收到了上一条消息的最后2个字节(d,k)和长度的前2个字节,导致结果为[107, 100, 26, 0]的字节切片,而实际上应该是[26, 0, 0, 0]
当然,剩下的数据包含了剩下的两个零:

TCP套接字:消息从先前的连接接收消息的部分。

英文:

I've accidentally spotted a bug when parts of a message from previous connection go to the next message.

I have a basic server with client. I have removed all the error handling to avoid bloating the examples too much.

Also I've replaced some Printf's with time.Sleep since I just don't have a chance to break the connection in time to reproduce the bug because it reads the data too fast.

The "package" is a simple structure, where the first 4 bytes is the length and then goes the content.

Client code:

package main

import (
	&quot;encoding/binary&quot;
	&quot;fmt&quot;
	&quot;net&quot;
)

func main() {
	conn, _ := net.Dial(&quot;tcp&quot;, &quot;0.0.0.0:8081&quot;)
	defer conn.Close()

	str := &quot;msadsakdjsajdklsajdklsajdk&quot;

	// Creating a package
	buf := make([]byte, len(str)+4)
	copy(buf[4:], str)
	binary.LittleEndian.PutUint32(buf[:4], uint32(len(str)))

	for {
		_, err := conn.Write(buf)
		if err != nil {
			fmt.Println(err)
			return
		}
	}
}

Server code:

package main

import (
	&quot;encoding/binary&quot;
	&quot;fmt&quot;
	&quot;net&quot;
	&quot;sync&quot;
	&quot;time&quot;
)

func ReadConnection(conn net.Conn, buf []byte) (err error) {
	maxLen := cap(buf)
	readSize := 0
	for readSize &lt; maxLen {
		// instead of Printf
		time.Sleep(time.Nanosecond * 10)

		readN, err := conn.Read(buf[readSize:])
		if err != nil {
			return err
		}
		readSize += readN
	}
	return nil
}

func handleConnection(conn net.Conn, waitGroup *sync.WaitGroup) {
	waitGroup.Add(1)
	defer conn.Close()
	defer waitGroup.Done()

	fmt.Printf(&quot;Serving %s\n&quot;, conn.RemoteAddr().String())

	var packageSize int32 = 0
	int32Buf := make([]byte, 4)

	for {
		// read the length
		conn.Read(int32Buf)
		packageSize = int32(binary.LittleEndian.Uint32(int32Buf))

        // assuming the length should be 26
        if packageSize &gt; 26 {
            fmt.Println(&quot;Package size error&quot;)
            return
        }

		// read the content
		packageBuf := make([]byte, packageSize)
		if err := ReadConnection(conn, packageBuf); err != nil {
			fmt.Printf(&quot;ERR: %s\n&quot;, err)
			return
		}

		// instead of Printf
		time.Sleep(time.Nanosecond * 100)
	}
}

func main() {
	//establish connection
	listener, _ := net.Listen(&quot;tcp&quot;, &quot;0.0.0.0:8081&quot;)
	defer listener.Close()

	waitGroup := sync.WaitGroup{}
	for {
		conn, err := listener.Accept()
		if err != nil {
			break
		}

		go handleConnection(conn, &amp;waitGroup)
	}

	waitGroup.Wait()
}

So for some reason, int32Buf receives the last 2 bytes from a previous message (d, k) and the first 2 bytes of the length, resulting in [107, 100, 26, 0] bytes slice, when it should be [26, 0, 0, 0].
And of course, the rest of the data contains remaining two zeroes:

TCP套接字:消息从先前的连接接收消息的部分。

答案1

得分: 2

你需要检查conn.Read的返回值,并将其与你的期望进行比较。在你的代码中,你假设conn.Read总是会完全填充给定的4字节缓冲区。

这个假设是错误的,也就是说,它实际上可能读取的数据量较少。具体来说,它可能只读取2个字节,这样你的缓冲区中就会有\x1a\x00\x00\x00,这仍然对应着消息长度为26。只是,消息的前2个字节实际上是长度的最后2个字节,它们没有包含在最后一次读取中。这意味着在读取完26个字节后,它并没有读取完整个消息。还剩下2个字节,将被包含在下一个消息中 - 这就是你观察到的情况。

为了确保确切地读取了缓冲区的大小,请检查conn.Read的返回值,或者使用io.ReadFull。在你这样做之后,它将按预期工作(根据评论):

> 好的,现在它完美地工作了

那么为什么这只在新连接的上下文中发生呢?也许是因为由于另一个连接的额外负载而稍微改变了行为,但足够显著。然而,这些数据并不是来自不同连接的数据,而是来自当前连接,与问题描述中的描述相反。通过使用不同的客户端和不同的消息,可以轻松地进行检查。

英文:

> conn.Read(int32Buf)

You need to check the return value of conn.Read and compare it against your expectations. You are assuming in your code that conn.Read will always completely fill the given buffer of 4 bytes.

This assumption is wrong, i.e. it might actually read less data. Specifically it might read only 2 bytes in which case you'll end up with \x1a\x00\x00\x00 in your buffer which still translates to a message length of 26. Only, the first 2 bytes of the message will actually be the last 2 bytes of the length which were not included in the last read. This means after reading the 26 bytes it will not have read the full message. 2 bytes are legt and will be included into the next message - this is what you observed.

To be sure that the exact size of the buffer is read check the return values of conn.Read or use io.ReadFull. After you've done this it works as expected (from the comment):

> Ok, now it works perfect

So why does this happened only in context of a new connection? Maybe because the additional load due to another connection changed the behavior slightly but significantly enough. Still, these are not the data read from a different connection but data from the current one contrary to the description in the question. This could be easily checked by using different messages with different clients.

huangapple
  • 本文由 发表于 2022年10月17日 03:14:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/74090036.html
匿名

发表评论

匿名网友

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

确定