Golang中解决Tcp粘包的问题

文章目录

  • 1、问题分析
  • 2、解决办法
  • 3、代码实现
    • 3.1 创建封装消息的接口及其实现类
    • 3.2 实现打包、拆包的接口及其实现类⭐️
    • 3.3 测试封包拆包的Server和Client

1、问题分析

在客户端传输的时候,如果我们想要进行消息的连发,或者说一次性发送多个消息包,就必要要解决Tcp粘包的问题。关于Tcp粘包在这里不作过多的讲解,另外本文主要是呈现解决思路,而并非实际的工业生产方案。
请添加图片描述

2、解决办法

在这里为了解决上述粘包的问题,我们需要将原来的数据再进行一层包装,其基本的逻辑概念如下:
请添加图片描述
当然这个具体设计细节需要跟随自己的业务进行改变,在这里我还设计了一个id,主要是方便我后面的一些相关业务。主要解决粘包的还是前面的datalen,读取时会首先读取len,根据这个len来读取后面具体长度的data,进而解决Tcp粘包的问题。

3、代码实现

创建一个项目

3.1 创建封装消息的接口及其实现类

  • 接口StickyBuns/isticky/imessage.go
    package istickytype IMessage interface {GetMsgID() uint32GetMsgLen() uint32GetData() []byte
    }
    
  • 实现类StickyBuns/sticky/message.go
    package sticky
    type Message struct {Id      uint32 // 消息IDDataLen uint32 // 消息的长度Data    []byte // 消息的内容
    }func (m *Message) GetMsgID() uint32 {return m.Id
    }func (m *Message) GetMsgLen() uint32 {return m.DataLen
    }func (m *Message) GetData() []byte {return m.Data
    }
    

3.2 实现打包、拆包的接口及其实现类⭐️

  • 接口StickyBuns/isticky/idatapack.go
    package istickytype IDataPack interface {GetHeadLen() uint32Pack(IMessage) ([]byte, error)UnPack([]byte) (IMessage, error)
    }
    
  • 接口实现类StickyBuns/sticky/datapack/go
    package stickyimport ("bytes""encoding/binary""fmt""v1/isticky"
    )type DataPack struct {
    }func (d *DataPack) GetHeadLen() uint32 {// 根据自身设计:len 4字节 + id 4字节return 8
    }// Pack 打包的实现
    func (d *DataPack) Pack(message isticky.IMessage) ([]byte, error) {// 创建一个bufferbuffer := bytes.NewBuffer([]byte{})// 将DataLen写入if err := binary.Write(buffer, binary.LittleEndian, message.GetMsgLen()); err != nil {fmt.Println("Failed to pack in write DataLen:", err)return nil, err}if err := binary.Write(buffer, binary.LittleEndian, message.GetMsgID()); err != nil {fmt.Println("Failed to pack in write MsgId:", err)return nil, err}if err := binary.Write(buffer, binary.LittleEndian, message.GetData()); err != nil {fmt.Println("Failed to pack in write Data:", err)return nil, err}return buffer.Bytes(), nil}// UnPack 拆包的实现
    func (d *DataPack) UnPack(binaryData []byte) (isticky.IMessage, error) {// 创建一个二进制的io.ReaderdataBuffer := bytes.NewReader(binaryData)// 解压Head信息msg := &Message{}// 读DataLenif err := binary.Read(dataBuffer, binary.LittleEndian, &msg.DataLen); err != nil {fmt.Println("Failed to unpack in read DataLen:", err)return nil, err}// 读Idif err := binary.Read(dataBuffer, binary.LittleEndian, &msg.Id); err != nil {fmt.Println("Failed to unpack in read id/:", err)return nil, err}// 注意到这里消息还没有解析return msg, nil
    }

3.3 测试封包拆包的Server和Client

StickyBuns/sticky/datapack_test.go

package stickyimport ("fmt""io""net""testing"
)func TestDataPack(t *testing.T) {/*模拟服务器*/listener, err := net.Listen("tcp", "127.0.0.1:8080")if err != nil {fmt.Println("Failed to listen at 127.0.0.1:8080:", err)return}go func() {for {// 进行Acceptconn, err := listener.Accept()if err != nil {fmt.Println("Failed to accept:", err)continue}go func(conn net.Conn) {dp := DataPack{}for {// 第一次从conn中读,把包的head(DataLen+Id)读取出来headData := make([]byte, dp.GetHeadLen())if _, err := io.ReadFull(conn, headData); err != nil {fmt.Println("Failed to unpack head:", err)return}// 第二次从conn中读取,之前head已经没有在conn中了,剩下的就是Data和其余消息体// headData里面只有头部,将其放入了拆包函数中,解析出DataLen和ID的具体取值// 尤其是DataLen他是关键的,因为后面需要该值来确定数据的长度msgHead, err := dp.UnPack(headData)if err != nil {fmt.Println("Failed to unpack:", err)return}msgData := make([]byte, msgHead.GetMsgLen())if _, err := io.ReadFull(conn, msgData); err != nil {fmt.Println("Failed to unpack data:", err)}fmt.Printf("---->DataLen=%d\tDataId=%d\tData=%s\n", msgHead.GetMsgLen(), msgHead.GetMsgID(), msgData)}}(conn)}}()/*模拟客户端*/conn, err := net.Dial("tcp", "127.0.0.1:8080")if err != nil {fmt.Println("Failed dial to 127.0.0.1:8080:", err)return}// 创建一个打包对象dp := DataPack{}// 新建一个消息体1msg1 := &Message{Id:      1,DataLen: 5,Data:    []byte("hello"),}sendMsg1, err := dp.Pack(msg1)if err != nil {fmt.Println("Failed to pack msg1:", err)return}msg2 := &Message{Id:      1,DataLen: 5,Data:    []byte("nihao"),}sendMsg2, err := dp.Pack(msg2)// 将两个消息一起发送sendMsg1 = append(sendMsg1, sendMsg2...)_, err = conn.Write(sendMsg1)if err != nil {return}// 主程序阻塞select {}
}

输出结果:

=== RUN   TestDataPack
---->DataLen=5	DataId=1	Data=hello
---->DataLen=5	DataId=1	Data=nihao

即实现了上述的功能。如果你的业务非常的复杂,那么在这里就需要按照自身的业务对消息进行封装,然后分别根据自己对消息的封装然后分别设计自身的封包和拆包的方法。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部