這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
golang中解決tcp傳輸中的粘包問題
Author: 嶽東衛
Email: usher.yue@gmail.com
什麼是粘包?
最近在寫https://github.com/UsherYue/ActivedRouter (一個http/https反向 Proxy服務)的時候遇到了粘包問題,
如果有做過網路編程的小夥伴應該都知道粘包問題,舉個例子: 比如用戶端在和服
務器進行通訊採用的是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, 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)