背景
在一次壓測(cè)的過(guò)程中峡谊,一直發(fā)現(xiàn)客戶(hù)端丟包茫虽,但是服務(wù)器端打日志檢查了相關(guān)的回包是一定下發(fā)了刊苍,因?yàn)槭莟cp的連接,是可靠連接濒析,不會(huì)出現(xiàn)丟包的現(xiàn)象正什,想了很久一直沒(méi)發(fā)現(xiàn)什么問(wèn)題,后面通過(guò)打印包長(zhǎng)度確定了出現(xiàn)了tcp粘包的情況号杏。讀取函數(shù)如下:
reply := make([]byte, 10240)
lenData, err := conn.Read(reply)
讀取的數(shù)據(jù)去解包的時(shí)候婴氮,會(huì)發(fā)現(xiàn)一直缺少一些包,當(dāng)延遲越小的時(shí)候盾致,出現(xiàn)的概率越大主经,因?yàn)檠舆t小,意味著回包越快庭惜,出現(xiàn)粘包的概率就越大罩驻。后面經(jīng)過(guò)查詢(xún)資料,發(fā)現(xiàn)了bufio.Scanner完美解決了這個(gè)問(wèn)題护赊,不需要我們手工去寫(xiě)切割包的代碼惠遏,這里有一個(gè)前提:一個(gè)完整包里面需要有字段去標(biāo)志你這個(gè)完整包的大小,這樣才能切割粘包骏啰。好了节吮,說(shuō)了這個(gè)多看看代碼。
解決方案
reply := make([]byte, 10240)
lenData, err := conn.Read(reply)
result := bytes.NewBuffer(nil)
result.Write(reply[0:lenData])
scanner := bufio.NewScanner(result)
split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if !atEOF {
// 這里寫(xiě)你的具體切割邏輯
var plen int
binary.Read(bytes.NewReader(data[0:10]), binary.BigEndian, &plen)
plen = int(binary.LittleEndian.Uint32(data[0:10]))
return plen, data[:plen], nil
}
return
}
scanner.Split(split)
for scanner.Scan() {
// 這里的scanner.Bytes()就是切割出來(lái)的每一個(gè)數(shù)據(jù)判耕,對(duì)應(yīng)這個(gè)split函數(shù)里面的第二個(gè)返回值
fmt.Println("recv:", string(scanner.Bytes()))
}
這樣就完美解決了粘包的問(wèn)題
原理
官方給的解釋是:
SplitFunc is the signature of the split function used to tokenize the input. The arguments are an initial substring of the remaining unprocessed data and a flag, atEOF, that reports whether the Reader has no more data to give. The return values are the number of bytes to advance the input and the next token to return to the user, if any, plus an error, if any.
Scanning stops if the function returns an error, in which case some of the input may be discarded.
Otherwise, the Scanner advances the input. If the token is not nil, the Scanner returns it to the user. If the token is nil, the Scanner reads more data and continues scanning; if there is no more data--if atEOF was true--the Scanner returns. If the data does not yet hold a complete token, for instance if it has no newline while scanning lines, a SplitFunc can return (0, nil, nil) to signal the Scanner to read more data into the slice and try again with a longer slice starting at the same point in the input.
The function is never called with an empty data slice unless atEOF is true. If atEOF is true, however, data may be non-empty and, as always, holds unprocessed text.
package "buffio"
函數(shù)原型:
type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)
Scanner是有緩存的课锌,意思是Scanner底層維護(hù)了一個(gè)Slice用來(lái)保存已經(jīng)從Reader中讀取的數(shù)據(jù),Scanner會(huì)調(diào)用我們?cè)O(shè)置SplitFunc祈秕,將緩沖區(qū)內(nèi)容(data)和是否已經(jīng)輸入完了(atEOF)以參數(shù)的形式傳遞給SplitFunc渺贤,而SplitFunc的職責(zé)就是根據(jù)上述的兩個(gè)參數(shù)返回下一次Scan需要前進(jìn)幾個(gè)字節(jié)(advance),分割出來(lái)的數(shù)據(jù)(token)请毛,以及錯(cuò)誤(err)志鞍。參考https://studygolang.com/articles/15474
這個(gè)bufio是個(gè)挺有意思的庫(kù),里面還有大量的api方仿,有時(shí)間可以詳細(xì)了解一下固棚,bufio官方文檔
待完成
這些只解決了粘包的現(xiàn)象,有關(guān)于半包的情況并沒(méi)有找到好的庫(kù)去解決仙蚜,半包情況還需要看一下