英文:
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]
。
当然,剩下的数据包含了剩下的两个零:
英文:
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 (
"encoding/binary"
"fmt"
"net"
)
func main() {
conn, _ := net.Dial("tcp", "0.0.0.0:8081")
defer conn.Close()
str := "msadsakdjsajdklsajdklsajdk"
// 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 (
"encoding/binary"
"fmt"
"net"
"sync"
"time"
)
func ReadConnection(conn net.Conn, buf []byte) (err error) {
maxLen := cap(buf)
readSize := 0
for readSize < 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("Serving %s\n", 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 > 26 {
fmt.Println("Package size error")
return
}
// read the content
packageBuf := make([]byte, packageSize)
if err := ReadConnection(conn, packageBuf); err != nil {
fmt.Printf("ERR: %s\n", err)
return
}
// instead of Printf
time.Sleep(time.Nanosecond * 100)
}
}
func main() {
//establish connection
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()
}
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:
答案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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论