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 packetimport ( "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 headerfunc (self *Packet) Setheader(header string) *Packet { self.header = header self.headerLengh = int32(len([]byte(header))) return self}//create default packagefunc NewDefaultPacket(data []byte) *Packet { return &Packet{DEFAulE_header,DEFAulT_header_LENGTH,DEFAulT_SAVE_DATA_LENGTH,data}}//convert to net packagefunc (self *Packet) Packet() []byte { return append(append([]byte(self.header),self.IntToBytes(int32(len(self.Data)))...),self.Data...)}//return value is sticky datafunc (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,&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)总结
以上是内存溢出为你收集整理的golang中解决tcp传输中的粘包问题全部内容,希望文章能够帮你解决golang中解决tcp传输中的粘包问题所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)