Go-TCP粘包


TCP黏包

黏包示例

服務端代碼如下:

// socket_stick/server/main.go

func process(conn net.Conn) {
    defer conn.Close()
    reader := bufio.NewReader(conn)  //讀取conn里接收的內容
    var buf [1024]byte
    for {
        n, err := reader.Read(buf[:])
        if err == io.EOF {
            break
        }
        if err != nil {
            fmt.Println("read from client failed, err:", err)
            break
        }
        recvStr := string(buf[:n])
        fmt.Println("收到client發來的數據:", recvStr)
    }
}

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/client/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?`
        conn.Write([]byte(msg))
    }
}

將上面的代碼保存后,分別編譯。先啟動服務端再啟動客戶端,可以看到服務端輸出結果如下:

收到client發來的數據: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
收到client發來的數據: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
收到client發來的數據: Hello, Hello. How are you?Hello, Hello. How are you?
收到client發來的數據: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
收到client發來的數據: Hello, Hello. How are you?Hello, Hello. How are you?

客戶端分10次發送的數據,在服務端並沒有成功的輸出10次,而是多條數據“粘”到了一起。

為什么會出現粘包

主要原因就是tcp數據傳遞模式是流模式,在保持長連接的時候可以進行多次的收和發。

“粘包”可發生在發送端也可發生在接收端

1. 由Nagle算法造成的發送端的粘包:Nagle算法是一種改善網絡傳輸效率的算法。簡單來說就是當我們提交一段數據給TCP發送時,TCP並不立刻發送此段數據,而是等待一小段時間看看在等待期間是否還有要發送的數據,若有則會一次把這兩段數據發送出去。

2. 接收端接收不及時造成的接收端粘包:TCP會把接收到的數據存在自己的緩沖區中,然后通知應用層取數據。當應用層由於某些原因不能及時的把TCP的數據取出來,就會造成TCP緩沖區中存放了幾段數據。

 

解決辦法

出現”粘包”的關鍵在於接收方不確定將要傳輸的數據包的大小,因此我們可以對數據包進行封包拆包的操作。

封包:封包就是給一段數據加上包頭,這樣一來數據包就分為包頭和包體兩部分內容了(過濾非法包時封包會加入”包尾”內容)。包頭部分的長度是固定的,並且它存儲了包體的長度,根據包頭長度固定以及包頭中含有包體長度的變量就能正確的拆分出一個完整的數據包。

我們可以自己定義一個協議,比如數據包的前4個字節為包頭,里面存儲的是發送的數據的長度。

// socket_stick/proto/proto.go
package proto

import (
    "bufio"
    "bytes"
    "encoding/binary"
)

// Encode 將消息編碼
func Encode(message string) ([]byte, error) {
    // 讀取消息的長度,轉換成int32類型(占4個字節)
    var length = int32(len(message))
    var pkg = new(bytes.Buffer)
    // 寫入消息頭
    err := binary.Write(pkg, binary.LittleEndian, length)
    if err != nil {
        return nil, err
    }
    // 寫入消息實體
    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) {
    // 讀取消息的長度
    lengthByte, _ := reader.Peek(4) // 讀取前4個字節的數據
    lengthBuff := bytes.NewBuffer(lengthByte)
    var length int32
    err := binary.Read(lengthBuff, binary.LittleEndian, &length)
    if err != nil {
        return "", err
    }
    // Buffered返回緩沖中現有的可讀取的字節數。
    if int32(reader.Buffered()) < length+4 {
        return "", err
    }

    // 讀取真正的消息數據
    pack := make([]byte, int(4+length))
    _, err = reader.Read(pack)
    if err != nil {
        return "", err
    }
    return string(pack[4:]), nil
}

接下來在服務端和客戶端分別使用上面定義的proto包的DecodeEncode函數處理數據。

服務端代碼如下:

// 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發來的數據:", 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)
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM