美文网首页
tcp 粘包的问题

tcp 粘包的问题

作者: myonlyzzy | 来源:发表于2018-10-11 18:14 被阅读0次

    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 粘包的问题

          本文链接:https://www.haomeiwen.com/subject/kvxiaftx.html