如何解决TCP通信中网络粘包问题

为何TCP信通中有粘包问题?

  1. 发送端需要等缓冲区满才发送出去,造成粘包。
  2. 接收方不及时接收缓冲区的包,造成多个包接收。

今天主要是解决问题,该问题背后的细节可以自行去学习网络编程的相关知识,废话不多说,下面通过代码演示如何解决该问题。

解决思路:

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
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值