• 解决TCP通信的黏包


    1、为什么会出现黏包?

    主要原因就是tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。

    2、如何解决黏包?

    出现”粘包”的关键在于接收方不确定将要传输的数据包的大小,因此我们可以对数据包进行封包和拆包的操作。

    封包:封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时封包会加入”包尾”内容)。包头部分的长度是固定的,并且它存储了包体的长度,根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。

    3、具体实现

    编码/解码

    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
    }
    

      

    服务端

    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)
        }
    }
    

      

    客户端

    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)
        }
    }
    

      

    参考:TCP黏包 · Go语言中文文档 (topgoer.com)

  • 相关阅读:
    html input type=file 选择图片,图片预览 纯html js实现图片预览
    asp.net mvc Controller控制器返回类型
    webrequest HttpWebRequest webclient/HttpClient
    js中__proto__和prototype constructor 的区别和关系
    JQuery的ajaxFileUpload的使用
    cuda中当数组数大于线程数的处理方法
    cuda中threadIdx、blockIdx、blockDim和gridDim的使用
    cuda和gcc版本不兼容
    【转】CentOS 6.6 升级GCC G++ (当前最新版本为v6.1.0) (完整)
    matlab练习程序(地图上画经纬度)
  • 原文地址:https://www.cnblogs.com/mango1997/p/16058732.html
Copyright © 2020-2023  润新知