什么是TCP粘包問(wèn)題
多個(gè)TCP包粘在一個(gè)成為一個(gè)包撑帖,服務(wù)端處理接收的TCP包時(shí),需要考慮拆包問(wèn)題澳眷。
產(chǎn)生原因
- 發(fā)送端胡嘿,如果發(fā)送端短時(shí)間發(fā)送多個(gè)小包,TCP默認(rèn)使用Nagle算法钳踊,將短時(shí)間的多個(gè)小包合并為一個(gè)大包發(fā)送給服務(wù)端
- 接收端衷敌,接收端接收的TCP并不會(huì)立即傳給應(yīng)用層處理,而是保存在緩存中拓瞪,應(yīng)用層會(huì)主動(dòng)去緩存中取數(shù)據(jù)處理缴罗,如果有多個(gè)包別緩存,應(yīng)用層一次就會(huì)拿多個(gè)包
什么時(shí)候考慮粘包問(wèn)題
- 如果是一個(gè)整體數(shù)據(jù)被拆成多個(gè)包發(fā)送祭埂,一般不需要考慮粘包問(wèn)題
- 多個(gè)不相同數(shù)據(jù)的包粘在一起面氓,即需要特別處理
如何處理粘包
- 發(fā)送端,可以關(guān)閉粘包功能,使用TCP_NODELAY選項(xiàng)來(lái)關(guān)閉Nagle算法
- 接收端舌界,接收端緩存問(wèn)題造成的粘包無(wú)法處理
- 解決辦法:從應(yīng)用層處理掘譬,
1:將發(fā)送數(shù)據(jù)重新封裝,把每個(gè)TCP包的長(zhǎng)度封裝上去
2:接收端將接收的數(shù)據(jù)重新解析
參考代碼Go語(yǔ)言版本:
在原始發(fā)送數(shù)據(jù)的前增加發(fā)送數(shù)據(jù)長(zhǎng)度部分
傳輸數(shù)據(jù)加密和解密
// socket_stick/proto/proto.go
package proto
import (
"bufio"
"bytes"
"encoding/binary"
)
// Encode 將消息編碼
func Encode(message string) ([]byte, error) {
// 讀取消息的長(zhǎng)度禀横,轉(zhuǎn)換成int32類(lèi)型(占4個(gè)字節(jié))
var length = int32(len(message))
var pkg = new(bytes.Buffer)
// 寫(xiě)入消息頭
err := binary.Write(pkg, binary.LittleEndian, length)
if err != nil {
return nil, err
}
// 寫(xiě)入消息實(shí)體
err = binary.Write(pkg, binary.LittleEndian, []byte(message))
if err != nil {
return nil, err
}
return pkg.Bytes(), nil
}
// Decode 解碼消息
func Decode(reader *bufio.Reader) (string, error) {
// 讀取消息的長(zhǎng)度
lengthByte, _ := reader.Peek(4) // 讀取前4個(gè)字節(jié)的數(shù)據(jù)
lengthBuff := bytes.NewBuffer(lengthByte)
var length int32
err := binary.Read(lengthBuff, binary.LittleEndian, &length)
if err != nil {
return "", err
}
// Buffered返回緩沖中現(xiàn)有的可讀取的字節(jié)數(shù)屁药。
if int32(reader.Buffered()) < length+4 {
return "", err
}
// 讀取真正的消息數(shù)據(jù)
pack := make([]byte, int(4+length))
_, err = reader.Read(pack)
if err != nil {
return "", err
}
return string(pack[4:]), nil
}
服務(wù)端代碼:
// socket_stick/server2/main.go
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
for {
msg, err := proto.Decode(reader)
if err == io.EOF {
return
}
if err != nil {
fmt.Println("decode msg failed, err:", err)
return
}
fmt.Println("收到client發(fā)來(lái)的數(shù)據(jù):", msg)
}
}
func main() {
listen, err := net.Listen("tcp", "127.0.0.1:30000")
if err != nil {
fmt.Println("listen failed, err:", err)
return
}
defer listen.Close()
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("accept failed, err:", err)
continue
}
go process(conn)
}
}
客戶端代碼:
// socket_stick/client2/main.go
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:30000")
if err != nil {
fmt.Println("dial failed, err", err)
return
}
defer conn.Close()
for i := 0; i < 20; i++ {
msg := `Hello, Hello. How are you?`
data, err := proto.Encode(msg)
if err != nil {
fmt.Println("encode msg failed, err:", err)
return
}
conn.Write(data)
}
}
UDP會(huì)不會(huì)有粘包問(wèn)題
不會(huì)
TCP是面向連接的粥血,為了性能柏锄,采用基于流的傳輸,基于流的傳輸复亏,不會(huì)認(rèn)為數(shù)據(jù)是一條條的趾娃,沒(méi)有保護(hù)數(shù)據(jù)的邊界,
UDP則是面向消息傳輸?shù)牡抻斜Wo(hù)消息邊界抬闷,所以不存在粘包溫恩提