Golang网络编程TCP粘包

2021-08-06  本文已影响0人  TZX_0710

服务端代码如下

package main

import (
    "bufio"
    "fmt"
    "net"
)
//处理函数
func process(conn net.Conn) {
    defer conn.Close() //关闭连接
    for {
        //获取输入流
        reader := bufio.NewReader(conn)
        //每次读取的大小
        var buf [1024]byte
        n, err := reader.Read(buf[:]) //读取数据 从头到尾读取
        if err != nil {
            fmt.Println("read from client failed,err", err)
            break
        }
        recvStr := string(buf[:n])
        fmt.Println("收到client端发来的数据:", recvStr)
        conn.Write([]byte(recvStr))
    }
}
func main() {
    listen, err := net.Listen("tcp", "127.0.0.1:30000")
    if err != nil {
        fmt.Println("listen failed,err", err)
        return
    }
    for {
        conn, err := listen.Accept()
        if err != nil {
            fmt.Println("accept failed,err", err)
            continue
        }
        go process(conn) //开启启程去处理读取数据
    }   
}

客户端

package main

import (
    "fmt"
    "net"
)

func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:30000")
    if err != nil {
        fmt.Println("diall error:", err)
        return
    }
    defer conn.Close()

    for i := 0; i < 20; i++ {
        msg := `Hello, Hello. How are you?`
        conn.Write([]byte(msg))
    }
}

result:

result

通过以上截图可以看出客户端写了19次的消息到服务端。但是服务端接受的数据把19条数据整合成了一条信息。把多条信息都粘在了一起。

为什么会出现粘包?
主要原因就是tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。"粘包"可以发送在发送端,也可发生在接收端:

解决办法
粘包主要的问题出现在接收方不确定要传输的数据包的大小。因此我们可以把数据包进行封包和拆包的操作
封包:封包就是给一段数据加上包头,这样以来数据包就分为包头和包体两部分内容了。包头部分的长度是固定的。并且存储了包体的长度,根据包头长度固定以及包含的包体长度 就能正确拆分出一个完整的数据包。

// Encode 将消息编码
func Encode(message string) ([]byte, error) {
    // 读取消息的长度,转换成int32类型(占4个字节)
    //长度是int32 为4个字节 也就是占包的前4位
    var length = int64(len(message))
    var pkg = new(bytes.Buffer)
    // 写入消息头
    //intDataSize write方法中根据第三个参数 data获取字节长度 int32 长度位4字节 int64的话就为8个字节可自行参考
    //不一定非要定义4个字节
    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) {
    // 读取消息的长度
      //可能在这边大家不是特别理解为什么指定读取8个字节 
//可参考上面ecode的write当中的intDataSize  方法。上面我才用了int64 占byte字节8字节。如果采用int32那么就是占4字节
    lengthByte, _ := reader.Peek(8) // 读取前8个字节
    lengthBuff := bytes.NewBuffer(lengthByte)
    var length int64
    err := binary.Read(lengthBuff, binary.LittleEndian, &length)
    if err != nil {
        return "", err
    }
    // Buffered返回缓冲中现有的可读取的字节数。
    if int64(reader.Buffered()) < length+8 {
        return "", err
    }

    // 读取真正的消息数据
    pack := make([]byte, int(8+length))
    _, err = reader.Read(pack)
    if err != nil {
        return "", err
    }
    return string(pack[8:]), 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)
    }
}
粘包
上一篇下一篇

猜你喜欢

热点阅读