为何TCP信通中有粘包问题?
- 发送端需要等缓冲区满才发送出去,造成粘包。
- 接收方不及时接收缓冲区的包,造成多个包接收。
今天主要是解决问题,该问题背后的细节可以自行去学习网络编程的相关知识,废话不多说,下面通过代码演示如何解决该问题。
解决思路:
1.因为收到信息的一方不清楚每条消息的边界,所以主要就是要解决读取规则问题。
2.通过TLV模式来设计包的结构。
3.首先将包设置成两个部分,头和身体,头部信息由消息类型和数据长度,如果嫌麻烦,可以只设计一个数据长度即可,这里设计了消息类型是为了方便根据不同的消息类型有不同的处理方法,整个头部这里设计成8个字节,其中消息类型和数据长度分别占4个字节,具体参看下图:
4. 后续读取的时,可以先读取8个字节,确定真实数据的有效长度,然后根据datalen长度去准确读取data信息即可。
具体代码如下,这里使用golang演示:
package main
import (
"bytes"
"encoding/binary"
"fmt"
"log"
"net"
"time"
)
func main() {
pack := NewPack()
listener, e := net.Listen("tcp", ":8888")
if e != nil {
panic(e)
}
//server 开启一个协程做作为服务端
go func() {
for {
conn, e := listener.Accept()
if e != nil {
log.Printf("accept error:%v\n",e)
return
}
go ConnHandler(conn)
}
}()
//client
conn, e := net.Dial("tcp", ":8888")
if e != nil {
panic(e)
}
msg := NewMsg(0,[]byte("hello world,I'm a client"))
res,e := pack.Package(msg)
if e != nil {
log.Printf("pack msg error:%v\n",e)
return
}
for{
//循环写入
conn.Write(res)
time.Sleep(time.Second)
}
}
//对conn进行处理
func ConnHandler(c net.Conn) {
// 读处理
go Reader(c)
//写处理 这里并没有具体实现,写的过程是跟客户端一样的,可以自行实现
go Writer(c)
}
func Writer(c net.Conn) {
}
func Reader(c net.Conn) {
header := make([]byte,8)
unpack := NewUnPack()
for{
_, err := c.Read(header)
if err != nil {
log.Printf("read header error:%v",err)
return
}
msg,err := unpack.UnPackage(header)
if err != nil {
log.Printf("unpack header error:%v",err)
return
}
if msg != nil && msg.MsgDataLen != 0 {
data := make([]byte,msg.MsgDataLen)
_,err := c.Read(len)
if err != nil {
log.Printf("read msg error:%v",err)
return
}
fmt.Println("receive the msg from client:",string(data))
}
}
}
type Msg struct {
MsgType int32
MsgDataLen int32
MsgData []byte
}
func NewMsg(msgtype int32,msg []byte) *Msg{
return &Msg{
MsgType:msgtype,
MsgDataLen:int32(len(msg)),
MsgData:msg,
}
}
type Pack struct {
}
//打包
func NewPack() *Pack{
return &Pack{}
}
func (this *Pack)Package(msg *Msg ) ([]byte, error) {
dataBuff := bytes.NewBuffer([]byte{})
//写msgType
if err := binary.Write(dataBuff, binary.LittleEndian, msg.MsgType); err != nil {
return nil, err
}
//写dataLen
if err := binary.Write(dataBuff, binary.LittleEndian,msg.MsgDataLen); err != nil {
return nil, err
}
//写data数据
if err := binary.Write(dataBuff, binary.LittleEndian, msg.MsgData); err != nil {
return nil, err
}
return dataBuff.Bytes(), nil
}
type UnPack struct {
}
func NewUnPack() *UnPack{
return &UnPack{}
}
//拆包
func (this *UnPack)UnPackage(binaryData []byte)(*Msg,error) {
//创建一个从输入二进制数据的ioReader
dataBuff := bytes.NewReader(binaryData)
//只解压head的信息,得到dataLen和msgID
msg := &Msg{}
//读msgType
if err := binary.Read(dataBuff, binary.LittleEndian, &msg.MsgType); err != nil {
return nil, err
}
//读dataLen
if err := binary.Read(dataBuff, binary.LittleEndian, &msg.MsgDataLen); err != nil {
return nil, err
}
//这里只需要把head的数据拆包出来就可以了,然后再通过head的长度,再从conn读取一次数据
return msg, nil
}