什么是 TCP 粘包
粘包问题是指当发送两条消息时,比如发送了 ABC 和 DEF,但另一端接收到的却是 ABCD,像这种一次性读取了两条数据的情况就叫做粘包(正常情况应该是一条一条读取的), 正确读取 ABC 和 DEF 两条信息。
当发送的消息是 ABC 时,另一端却接收到的是 AB 和 C 两条信息,像这种情况就叫做半包。
为什么会有粘包和半包?
这是因为 TCP 是面向连接的传输协议,TCP 传输的数据是以流的形式,而流数据是没有明确的开始结尾边界,所以 TCP 也没办法判断哪一段流属于一个消息。
造成粘包的主要原因
发送方每次写入数据 < 套接字(Socket)缓冲区大小
接收方读取套接字(Socket)缓冲区数据不够及时。
造成半包的主要原因
发送方每次写入数据 > 套接字(Socket)缓冲区大小
发送的数据大于协议的 MTU (Maximum Transmission Unit,最大传输单元),因此必须拆包。
怎么处理粘包?
fix length 处理粘包
package frame_decoder
import (
"fmt"
"math/rand"
"net"
"week9/protocol"
)
// ClientTcpFrameDecoder length field based frame decoder
func ClientTcpFrameDecoder(conn net.Conn) {
Log("client, length field based frame decoder")
for i := 0; i < 10; i++ {
userName := randStringRunes(6)
words := "{\"Name\":\"" + userName + "20211217\",\"Meta\":\"golang\",\"Content\":\"message\"}"
_, err := conn.Write(protocol.Packet([]byte(words)))
if err != nil {
fmt.Println(err, ",写入字符串错误 index=", i)
return
}
fmt.Println(words) // 打印发送出去的信息
}
Log("send over")
}
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func randStringRunes(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
执行程序在 client server 目录
每次发送固定缓冲区大小数据。客户端和服务器约定每次发送请求的大小。例如客户端发送 1024 个字节,服务器接受 1024 个字节。
这样虽然可以解决粘包的问题,但是如果发送的数据小于 1024 个字节,就会导致数据内存冗余和浪费;且如果发送请求大于 1024 字节,会出现半包的问题,也就是数据接收的不完整。
delimiter based 处理粘包
package fix_length
import (
"fmt"
"net"
)
func ServerTcpFixLength(server net.Conn) {
fmt.Println("server fixed length")
const BYTES = 1024
for {
var buf = make([]byte, BYTES)
_, err := server.Read(buf)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("client data: ", string(buf))
}
}
执行程序在 client server 目录
基于定界符来判断是不是一个请求(例如结尾’\n’). 客户端发送过来的数据,每次以 \n 结束,服务器每接受到一个 \n 就以此作为一个请求。然后对其拆分后的头部部分与前一个包的剩余部分进行合并,这样就得到了一个完整的包。这种方式的缺点在于如果数据量过大,查找定界符会消耗一些性能
length field based frame decoder 处理粘包
执行程序在 client server 目录
在 TCP 协议头里面写入每次发送请求的长度。 客户端在协议头里面带入数据长度,服务器在接收到请求后,根据协议头里面的数据长度来决定接受多少数据,只有在读取到足够长度的消息之后才算是读到了一个完整的消息。之后会按照参数指定的包长度偏移量数据对接收到的数据进行解码,从而得到目标消息体数据。