TCP黏包发生的主要原因是tcp是以数据流的形式进行传递的,在保持长连接的时候可以进行多次收发
黏包可能发生在发送端也可能发生在接收端
发送端:
1.由Nagle算法造成的发送端黏包:Nagle算法是一种改善网络传输效率的算法,简单来说,当我们提交一段数据给tcp发送的时候,TCP不立刻发送此数据,而是等待一小段时间看看等待时间内是否还有要发送的数据,若有则会一次性把这两段数据发送出去。既在等待时间内同时发送不满足MSS,(Maxitum Segment Size 这个最大值一般是)那么则会出现黏包现象。
if there is new data to send
if the window size >= MSS and available data is >= MSS
send complete MSS segment now
else
if there is unconfirmed data still in the pipe
enqueue data in the buffer until an acknowledge is received
else
send data immediately
end if
end if
end if
接收端:
接收端接受不及时造成的接收端黏包:TCP会把接收到的数据存到自己的缓存区中然后通知应用层来获取数据,当应用层不能及时取数据的时候,会造成TCP缓冲区存放了几段数据
示例:接收端黏包
package main
//接收端
import (
"fmt"
"net"
)
func processConn(conn net.Conn){
var tmp [128]byte
// 这里堵满了128就不会再往下读了
for {
n, err := conn.Read(tmp[:])
if err != nil {
fmt.Println("read error", err)
return
}
fmt.Println(string(tmp[:n]))
}
}
func main() {
// 本地端口启动服务 net包
listener, err := net.Listen("tcp", "127.0.0.1:20000")
if err != nil {
fmt.Println("监听端口失败: ", err)
return
}
//等待别人来跟我建立连接
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println(err)
return
}
// 与客户端进行通信
go processConn(conn)
}
}
发送端:
package main
import (
"fmt"
"net"
)
func main(){
conn,err := net.Dial("tcp","127.0.0.1:20000")
if err != nil{
fmt.Println(err)
return
}
defer conn.Close()
for i := 0;i < 20;i++{
msg := `Hello, Hello. How are you?`
conn.Write([]byte(msg))
}
}
结果:没有分20次接收 黏包现象出现
Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are yo
u?Hello, Hello. How are you?
Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are yo
u?Hello, Hello. How are you?
解决方法:
出现黏包的关键在于接收方不确定将要传输数据包的大小,因此我们可以对数据包进行分包和拆包 *** 作。
封包:就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时封包会加入"包尾那内容")。包头部分的长度是固定的,并且他存储了包体的长度,根据包头包含固定长度的变量就能正确拆分出一个完整的数据包,我们可以自己定义一个协议编码解码
package proto
import (
"bufio"
"bytes"
"encoding/binary"
)
// 将消息编码
func Encode(msg string)([]byte,error){
// 读取消息长度,转化为int32(4字节)
var length = int32(len(msg))
var pkg = new(bytes.Buffer)
err := binary.Write(pkg,binary.LittleEndian,length)
if err != nil{
return nil,err
}
err = binary.Write(pkg,binary.LittleEndian,[]byte(msg))
if err != nil{
return nil,err
}
return pkg.Bytes(),nil
}
func Decode(reader bufio.Reader)(string,error){
// 读取消息的长度,前4字节
lengthByte,_ := reader.Peek(4)
lengthBuff := bytes.NewBuffer(lengthByte)
var length int32
err := binary.Read(lengthBuff,binary.LittleEndian,&length)
if err != nil{
return "",err
}
// bufferd返回缓冲区现有可读字节数
if int32(reader.Buffered()) < length + 4{
return "", err
}
//读取真正的消息数据
pack := make([]byte,int(4+length))
_,err = reader.Read(pack)
if err != nil{
return "",err
}
return string(pack[4:]),nil
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)