golang中解决tcp传输中的粘包问题

简介: golang中解决tcp传输中的粘包问题Author: 岳东卫 Email: usher.yue@gmail.com 什么是粘包?最近在写https://github.com/UsherYue/ActivedRouter (一个http/https反向代理服务)的时候遇到了粘包问题, 如果有做过网络编程的小伙伴应该都知道粘包问题,举个例子: 比如客户端在和服 务器进行通信采用的是json格式的数据包。

golang中解决tcp传输中的粘包问题

Author: 岳东卫
Email: usher.yue@gmail.com

什么是粘包?

最近在写https://github.com/UsherYue/ActivedRouter (一个http/https反向代理服务)的时候遇到了粘包问题,

如果有做过网络编程的小伙伴应该都知道粘包问题,举个例子: 比如客户端在和服

务器进行通信采用的是json格式的数据包。那么此时Client和Server的数据交互流程应该如下:

Client Send Json Data->经过网络->Server Reveive Data->Server Decode Json ->Done (一次交互只有一个Json数据包)

上述流程我们假设从客户端发送到服务器接收这个一次的性动作中间交互的数

据是一个完成的json数据包,因此我们的程序可以正常工作。

但是实际情况并不是我们想的这样,由于TCP协议的特点、以及网络环境的复杂

多变、以及服务器对客户端的数据接收处理不及时等等原因,会导致网络传输

过程中出现粘包。 也就是说在服务器进行一次数据读取的时候,我们假想这

个数据包是一个完整的json数据包,但是实际上他确实 ,2个Json 数据包、3

个json数据包、2.5个json数据包,这就是我们所说的粘包。
如果你还不能理解那么看下图。

这里写图片描述

我们如何解决粘包问题?

我们在开发过程中通常会在server端接收数据的时候定义一个固定长度的buffer来存储从客户端连接发来的数据包 ,然后对这个数据包进行反序列化,所以要解决这个问题我们就要从收发数据的时候做一些手脚, 思路如下:

Client Send Json Data->调用封装方法将数据封装成固定格式的Packet->经过网络->Server Reveive Data->调用解封装方法取出粘包packet中所有json数据包,并将剩余截断数据和下一次到来的数据包进行拼接->Server Decode Json ->Done (一次交互只有一个Json数据包)

我在golang中实现了一个Packet封装代码如下,可直接使用:

package packet

import (
    "bytes"
    "encoding/binary"
)

const (
    DEFAULE_HEADER           = "[**********]"
    DEFAULT_HEADER_LENGTH    = 12
    DEFAULT_SAVE_DATA_LENGTH = 4
)

type Packet struct {
    Header         string
    HeaderLengh    int32
    SaveDataLength int32
    Data           []byte
}

//set delimiter header
func (self *Packet) SetHeader(header string) *Packet {
    self.Header = header
    self.HeaderLengh = int32(len([]byte(header)))
    return self
}

//create default package
func NewDefaultPacket(data []byte) *Packet {
    return &Packet{DEFAULE_HEADER, DEFAULT_HEADER_LENGTH, DEFAULT_SAVE_DATA_LENGTH, data}
}

//convert to net package
func (self *Packet) Packet() []byte {
    return append(append([]byte(self.Header), self.IntToBytes(int32(len(self.Data)))...), self.Data...)
}

//return value is sticky data
func (self *Packet) UnPacket(readerChannel chan []byte) []byte {
    dataLen := int32(len(self.Data))
    var i int32
    for i = 0; i < dataLen; i++ {
        //Termiate for loop when the remaining data is insufficient .
        if dataLen < i+self.HeaderLengh+self.SaveDataLength {
            break
        }
        //find Header
        if string(self.Data[i:i+self.HeaderLengh]) == self.Header {
            saveDataLenBeginIndex := i + self.HeaderLengh
            actualDataLen := self.BytesToInt(self.Data[saveDataLenBeginIndex : saveDataLenBeginIndex+self.SaveDataLength])
            //The remaining data is less than one package
            if dataLen < i+self.HeaderLengh+self.SaveDataLength+actualDataLen {
                break
            }
            //Get a packet
            packageData := self.Data[saveDataLenBeginIndex+self.SaveDataLength : saveDataLenBeginIndex+self.SaveDataLength+actualDataLen]
            //send pacakge data to reader channel
            readerChannel <- packageData
            //get next package index
            i += self.HeaderLengh + self.SaveDataLength + actualDataLen - 1
        }
    }
    //Reach the end
    if i >= dataLen {
        return []byte{}
    }
    //Returns the remaining data
    return self.Data[i:]
}

func (self *Packet) IntToBytes(i int32) []byte {
    byteBuffer := bytes.NewBuffer([]byte{})
    binary.Write(byteBuffer, binary.BigEndian, i)
    return byteBuffer.Bytes()
}

func (self *Packet) BytesToInt(data []byte) int32 {
    var val int32
    byteBuffer := bytes.NewBuffer(data)
    binary.Read(byteBuffer, binary.BigEndian, &val)
    return val
}

Client实现伪代码代码如下:

  dataPackage := NewDefaultPacket([]byte(jsonString)).Packet()
  Client.Write(dataPackage)

Server实现伪代码代码如下:

  //Declare a pipe for receiving unpacked data
    readerChannel := make(chan []byte, 1024)
    //Store truncated data
    remainBuffer := make([]byte, 0)
    //read unpackage data from buffered channel
    go func(reader chan []byte) {
        for {
            packageData := <-reader
            //....balabala....
        }
    }(readerChannel)
  remainBuffer =   NewDefaultPacket(append(remainBuffer,recvData)).UnPacket(readerChannel)
目录
相关文章
|
17天前
|
网络协议 Linux Go
Go语言TCP Socket编程(下)
Go语言TCP Socket编程
|
17天前
|
网络协议 Ubuntu Unix
Go语言TCP Socket编程(上)
Go语言TCP Socket编程
|
4月前
|
网络协议 NoSQL Go
Golang实现redis系列-(2)基础的TCP
Golang实现redis系列-(2)基础的TCP
27 0
|
9月前
|
网络协议 程序员 Go
GO语言使用之网络编程(TCP编程)
GO语言使用之网络编程(TCP编程)
66 0
|
8月前
|
网络协议 Go
GO语言学习 - 创建TCP监听
GO语言学习 - 创建TCP监听
235 1
|
11月前
|
存储 网络协议 Go
学习golang(1) 初探:写一个简单的TCP服务器
学习golang(1) 初探:写一个简单的TCP服务器
82 0
|
网络协议
TCP/UDP相关-三次握手四次挥手以及为什么三次握手-如何实现可靠UDP传输
TCP/UDP相关-三次握手四次挥手以及为什么三次握手-如何实现可靠UDP传输
111 0
|
缓存 网络协议 算法
四十一、TCP可靠传输、流量控制、拥塞控制
四十一、TCP可靠传输、流量控制、拥塞控制
四十一、TCP可靠传输、流量控制、拥塞控制
|
缓存 网络协议 算法
计算机网络学习26:TCP/UDP对比区别、TCP流量控制、拥塞控制、超时重传时间的选择、可靠传输的实现
UDP: User Datagram Protocol 用户数据报协议 TCP: Transmission Control Protocol 传输控制协议 同时这里指的连接是指逻辑连接,而不是物理连接。
计算机网络学习26:TCP/UDP对比区别、TCP流量控制、拥塞控制、超时重传时间的选择、可靠传输的实现
|
网络协议 算法 程序员
坦白局,TCP粘包:我只是犯了每个数据包都会犯的错
李东,自称亚健康终结者,尝试使用互联网+的模式拓展自己的业务。在某款新开发的聊天软件琛琛上发布广告。 键盘说来就来。疯狂发送"李东",回车发送!,"亚健康终结者",再回车发送!