前言
一般所謂的TCP粘包是在一次接收數(shù)據(jù)不能完全地體現(xiàn)一個完整的消息數(shù)據(jù)。TCP通訊為何存在粘包呢?主要原因是TCP是以流的方式來處理數(shù)據(jù),再加上網(wǎng)絡(luò)上MTU的往往小于在應(yīng)用處理的消息數(shù)據(jù),所以就會引發(fā)一次接收的數(shù)據(jù)無法滿足消息的需要,導(dǎo)致粘包的存在。處理粘包的唯一方法就是制定應(yīng)用層的數(shù)據(jù)通訊協(xié)議,通過協(xié)議來規(guī)范現(xiàn)有接收的數(shù)據(jù)是否滿足消息數(shù)據(jù)的需要。在應(yīng)用中處理粘包的基礎(chǔ)方法主要有兩種分別是以4節(jié)字描述消息大小或以結(jié)束符,實(shí)際上也有兩者相結(jié)合的如HTTP,redis的通訊協(xié)議等。
應(yīng)用場景
大部分TCP通訊場景下,使用自定義通訊協(xié)議
粘包處理原理:通過請求頭中數(shù)據(jù)包大小,將客戶端N次發(fā)送的數(shù)據(jù)緩沖到一個數(shù)據(jù)包中
例如:
請求頭占3個字節(jié)(指令頭1字節(jié)、數(shù)據(jù)包長度2字節(jié)),版本占1個字節(jié),指令占2個字節(jié)
協(xié)議規(guī)定一個數(shù)據(jù)包最大是512字節(jié),請求頭中數(shù)據(jù)包記錄是1300字節(jié),完整的數(shù)據(jù)包是1307個字節(jié),此時服務(wù)器端需要將客戶端3次發(fā)送數(shù)據(jù)進(jìn)行粘包處理
代碼示例
package server
import (
"net"
"bufio"
"ftj-data-synchro/protocol"
"golang.org/x/text/transform"
"golang.org/x/text/encoding/simplifiedchinese"
"io/ioutil"
"bytes"
"ftj-data-synchro/logic"
"fmt"
"strconv"
)
/*
客戶端結(jié)構(gòu)體
*/
type Client struct {
DeviceID string //客戶端連接的唯標(biāo)志
Conn net.Conn //連接
reader *bufio.Reader //讀取
writer *bufio.Writer //輸出
Data []byte //接收數(shù)據(jù)
}
func NewClient(conn *net.TCPConn) *Client {
reader := bufio.NewReaderSize(conn, 10240)
writer := bufio.NewWriter(conn)
c := Client{Conn:conn, reader:reader, writer:writer}
return c
}
/**
數(shù)據(jù)讀取(粘包處理)
*/
func (this *Client)read() {
for {
var data []byte
var err error
//讀取指令頭 返回輸入流的前4個字節(jié),不會移動讀取位置
data, err = this.reader.Peek(4)
if len(data) == 0 || err != nil {
continue
}
//返回緩沖中現(xiàn)有的可讀取的字節(jié)數(shù)
var byteSize = this.reader.Buffered()
fmt.Printf("讀取字節(jié)長度:%d\n", byteSize)
//生成一個字節(jié)數(shù)組,大小為緩沖中可讀字節(jié)數(shù)
data = make([]byte, byteSize)
//讀取緩沖中的數(shù)據(jù)
this.reader.Read(data)
fmt.Printf("讀取字節(jié):%d\n", data)
//保存到新的緩沖區(qū)
for _, v := range data {
this.Data = append(this.Data, v)
}
if len(this.Data) 4 {
//數(shù)據(jù)包緩沖區(qū)清空
this.Data = []byte{}
fmt.Printf("非法數(shù)據(jù),無指令頭...\n")
continue
}
data, err = protocol.HexBytesToBytes(this.Data[:4])
instructHead, _ := strconv.ParseUint(string(data), 16, 16)
//指令頭效驗(yàn)
if uint16(instructHead) != 42330 {
fmt.Printf("非法數(shù)據(jù)\n")
//數(shù)據(jù)包緩沖區(qū)清空
this.Data = []byte{}
continue
}
data = this.Data[:protocol.HEADER_SIZE]
var p = protocol.Decode(data)
fmt.Printf("消息體長度:%d\n", p.Len)
var bodyLength = len(this.Data)
/**
判斷數(shù)據(jù)包緩沖區(qū)的大小是否小于協(xié)議請求頭中數(shù)據(jù)包大小
如果小于,等待讀取下一個客戶端數(shù)據(jù)包,否則對數(shù)據(jù)包解碼進(jìn)行業(yè)務(wù)邏輯處理
*/
if int(p.Len) > len(this.Data) - protocol.HEADER_SIZE {
fmt.Printf("body體長度:%d,讀取的body體長度:%d\n", p.Len, bodyLength)
continue
}
fmt.Printf("實(shí)際處理字節(jié):%v\n", this.Data)
p = protocol.Decode(this.Data)
//邏輯處理
go this.logicHandler(p)
//數(shù)據(jù)包緩沖區(qū)清空
this.Data = []byte{}
}
}
待優(yōu)化部分:
type Client struct {
DeviceID string //客戶端連接的唯標(biāo)志
Conn net.Conn //連接
reader *bufio.Reader //讀取
writer *bufio.Writer //輸出
Data []byte //接收數(shù)據(jù)
}
結(jié)構(gòu)體中Data屬性可考慮使用bytes.Buffer
實(shí)現(xiàn)。
Golang標(biāo)準(zhǔn)庫文檔:https://studygolang.com/pkgdoc
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
您可能感興趣的文章:- Go語言服務(wù)器開發(fā)之簡易TCP客戶端與服務(wù)端實(shí)現(xiàn)方法
- GO語言實(shí)現(xiàn)簡單TCP服務(wù)的方法
- golang之tcp自動重連實(shí)現(xiàn)方法
- 利用Golang實(shí)現(xiàn)TCP連接的雙向拷貝詳解
- 6行代碼快速解決golang TCP粘包問題
- Go語言中TCP/IP網(wǎng)絡(luò)編程的深入講解