美文网首页
Golang网络编程TCP粘包

Golang网络编程TCP粘包

作者: TZX_0710 | 来源:发表于2021-08-06 09:21 被阅读0次

服务端代码如下

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数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。"粘包"可以发送在发送端,也可发生在接收端:

  • 由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。
  • 接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成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)
    }
}
粘包

相关文章

  • Golang网络编程TCP粘包

    服务端代码如下 客户端 result: result通过以上截图可以看出客户端写了19次的消息到服务端。但是服务端...

  • golang 解决 TCP 粘包问题

    什么是 TCP 粘包问题以及为什么会产生 TCP 粘包,本文不加讨论。本文使用 golang 的 bufio.Sc...

  • Golang网络编程TCP连接

    Golang网络编程 TCP编程编写服务端package mainimport ( "bufio" "fmt"...

  • 从应用编程到网络

    在公司内部分享的PPT,关于经典网络基础问题。包括: 所谓”tcp粘包“问题、tcp客户端经典编程、端口复用、客户...

  • TCP粘包处理

    TCP粘包 TCP粘包的处理

  • Netty原理(五)Netty中粘包和拆包的解决方案

    1.粘包和拆包 粘包和拆包是TCP网络编程中不可避免的,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需...

  • Java面试——TCP/IP

    参考资料:[1]. 网络编程之accept函数和accept函数在三次握手中的位置[2]. TCP 粘包/拆包的原...

  • JAVA-每日一面 2022-01-25

    什么是 TCP 粘包/拆包以及TCP 粘包/拆包的解决办法 TCP 粘包/拆包1、要发送的数据大于 TCP 发送缓...

  • Netty中粘包和拆包的解决方案

    粘包和拆包是TCP网络编程中不可避免的,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层...

  • netty粘包和拆包

    粘包和拆包是TCP网络编程中不可避免的,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层...

网友评论

      本文标题:Golang网络编程TCP粘包

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