tcp 粘包的问题

tcp服务是一个流数据的双工服务,tcp client 端会源源不断的把二进制字节流发发给服务端.假设有下面一个这样的需求,我们需要发送一个用户信息给服务端.我们怎么样知道到那里是一个客户端发来的一个完整的用户信息呢,如果我们不认真处理这个问题就会产生粘包的问题,对于这个问题一般会有下面几种方式来解决.

  • 使用分格符
    如果我们约定一个分隔符作为我们2个用户数据之间的分隔,如果读到这个符号我们就约定一个完整的数据结构读完了.
  • 给数据头部添加数据部分长度,先读取长度,再根据这个长度数据剩下的数据.

定义协议

type Packet struct {
    Version   [2]byte
    Length    int16
    Timestamp int64
    Message   []byte
}

在这个协议中我们使用前2个字节表示协议版本,2个字节表示数据长度,8个字节表示时间戳,前12字节是属于协议头部部分,剩下的是数据部分.

server端

package main

import (
    "net"
    "log"
    "fmt"
    "encoding/binary"
    "time"

)

const (
    ProtocolVersion = "V1"
)

func main() {

    l, err := net.Listen("tcp", ":8090")
    if err != nil {
        log.Println(err)
    }
    defer l.Close()
    for {
        conn, err := l.Accept()
        if err != nil {
            log.Println(err)
        }
        go handlerConn(conn)
    }

}

func handlerConn(conn net.Conn) {
    defer conn.Close()
    header := make([]byte, 12)
    //var u user.User
    for {
        //read packet header
        _, err := conn.Read(header)
        if err != nil {
            return
        }
        if fmt.Sprintf("%s", header[:2]) != ProtocolVersion {
            log.Println("valid protoc version")
            return
        }
        timestamp := binary.BigEndian.Uint64(header[2:10])
        t := time.Unix(int64(timestamp), 0).Format("2006-01-02 03:04:05 PM")
        log.Printf("client send data time %s", t)
        length := int16(binary.BigEndian.Uint16(header[10:]))
        log.Println("data length", length)
        //read data
        databuf := make([]byte, length)
        _, err = conn.Read(databuf)
        if err != nil {
            return
        }
        fmt.Printf("%s\n", databuf)
        conn.Write(databuf)
    }

}

在server端先读取12个字节,然后分别读取出版本号和时间戳,数据长度.然后读取数据.

client 端

package main

import (
    "net"

    "log"
    "time"
    "math/rand"
    "encoding/binary"
    "github.com/myonlyzzy/go-exmaple/example5/pkg/user"
    "encoding/json"
)
type User struct {
    Name string `json:"name"`
    Age  int64  `json:"age"`
    Msg  string `json:"msg"`
}

func main() {
    addr := ":8090"
    conn, err := net.Dial("tcp", addr)
    if err != nil {
        log.Println(err)
    }
    for {
        msg := getRandString()
        u := user.User{
            Name: "john",
            Age:  24,
            Msg:  msg,
        }
        data, err := json.Marshal(u)
        if err != nil {
            log.Fatal(err)
        }
        dataLen := len(data)
        b := make([]byte, dataLen+12)
        b[0] = 'V'
        b[1] = '1'
        binary.BigEndian.PutUint64(b[2:10], uint64(time.Now().Unix()))
        binary.BigEndian.PutUint16(b[10:12], uint16(dataLen))
        copy(b[12:], data)
        _, err = conn.Write(b)
        if err != nil {
            log.Fatal(err)
        }
        time.Sleep(time.Second)
    }

}

func getRandString() string {
    length := rand.Intn(5000)
    strBytes := make([]byte, length)
    for i := 0; i < length; i++ {
        strBytes[i] = byte(rand.Intn(26) + 97)
    }
    return string(strBytes)
}

client端先将表示用户数据的json转成字节流然后取的长度,然后再分别填入头部信息。

总结

这是一个最简单的tcp数据包的结构定义,但基本原理就是这样。

你可能感兴趣的:(tcp 粘包的问题)