golang中解決tcp傳輸中的粘包問題
Author: 嶽東衛
Email: [email protected]
什麼是粘包?
最近在寫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)