TCP粘包原理及解決方案
阿新 • • 發佈:2021-06-23
一、粘包是什麼
兩個程式能夠互相通訊是採用了套接字(socket)技術,socket在傳送端和接收端都有個快取機制,傳送端在把需要傳送的資料先放在快取上,等資料超過快取大小時,就會打包發給接收端;接收端接到資料也會先放到快取,再根據應用程式(recv/read)去讀取這些資料,直到讀完快取上的資料。
TCP是一個流協議,TCP只保證把要傳送的資料按包序號完整的傳送給接收端,接收端讀取資料的時候會按應用程式上設定好的大小去讀取,假如接收端每次按照300位元組大小去讀,實際業務需要資料600個位元組,那麼接收端讀取一次是沒有把應用需要的資料讀取完整,而需讀取兩次;再假如實際業務需要資料100個位元組,接收端按照300位元組大小去讀取,會把業務上分為兩個含義資料一次就讀取出來了,這就產生了粘包的問題,所以粘包問題的本質就是資料讀取邊界錯誤所致。
二、位元組序
位元組序是在記憶體中位元組儲存順序,分為大端序(網路位元組序)和小端序(主機位元組序);
高位位元組:在ASCII上開始的位元組,如0x00
低位位元組:在ASCII上最後的位元組,如0x7F
大端序:高位位元組存入低地址;
小端序:低位位元組存入低地址;
三、解決方案
上面所說的應用資料,是在TCP資料包裡面的,應用資料的資料格式由使用者自己協定,比如http協議,有頭部和包體,頭部有設定資料長度,包體則是實際資料 。
那麼解決粘包問題思路:
1,設定頭部資訊,長度固定;
2,使用特定字元作為分隔符,用來分割資料邊界問題;
3,新增除頭部資訊的資料長度。
下面用golang程式碼結合上面3點思路解決粘包問題:
傳送端封裝資料
//定義頭部資訊固定長度為10,分隔符兩個字元:0x43, 0x53 headData := []byte{0x43, 0x53, 0, 0, 0, 0, 0, 0, 0, 0} //實際資料 context := "測試內容" //實際資料長度用4個位元組,小端序方式來儲存 binary.LittleEndian.PutUint32(headData[2:6], uint32(len(context))) //資料包(頭部資訊+實際資料) data := make([]byte, 0) data = append(data, headData...) data = append(data, context...) fmt.Println(data) //輸出 [67 83 12 0 0 0 0 0 0 0 230 181 139 232 175 149 229 134 133 229 174 185] //67 83 12 0 0 0 0 0 0 0 為頭部資訊;230 181 139 232 175 149 229 134 133 229 174 185 為實際資料
接收端拆分資料
// 讀取前10個位元組的資料(表示資料包頭部的資訊)
// peek操作只讀資料,但不會移動資料位置!(沒有資料則阻塞)
headData, err := reader.Peek(10)
fmt.Println(headData)//[67 83 12 0 0 0 0 0 0 0]
if err != nil {
return "", err
}
//驗證頭部分割符
if headData[0] != 0x43 || headData[1] != 0x53 {
err = errors.New(fmt.Sprintf("headData[0]: %d,headData[1]: %d", headData[0], headData[1]))
return "", err
}
headLen := len(headData)
//驗證頭部長度
if headLen < 10 {
err = errors.New(fmt.Sprintf("headData len :%d", headLen))
return "", err
}
//獲取實際資料長度byte
lengthByte := headData[2:6]//[12 0 0 0]
lengthBuf := bytes.NewBuffer(lengthByte)
var dataLen int32
//小端序方式轉換實際資料長度數字
err = binary.Read(lengthBuf, binary.LittleEndian, &dataLen)
if err != nil {
return "", err
}
//資料包長度(頭部資訊長度+實際資料長度)
packLen := 10 + dataLen
pack := make([]byte, packLen)
// 判斷快取資料是否滿足一個完整資料包
buffLen := int32(reader.Buffered())
if buffLen < packLen {
cacheBuff := make([]byte, 0)
dataBuff := make([]byte, buffLen, buffLen)
for {
n, err := reader.Read(dataBuff)
cacheBuff = append(cacheBuff, dataBuff[0:n]...)
if err == nil {
if int32(len(cacheBuff)) < packLen {
num := packLen - int32(len(cacheBuff))
dataBuff = make([]byte, num, num)
continue
}
} else {
return "", err
}
pack = cacheBuff
break
}
} else {
// 讀取整個資料包
_, err = reader.Read(pack)
if err != nil {
fmt.Println("2")
return "", err
}
}
// str, _ := DecodeData(pack[10:])
str := pack[10:]
fmt.Println(string(data)//輸出:測試內容