Go语言网络编程(socket编程)TCP粘包

2023-10-27

1、TCP粘包

服务端代码如下:

// socket_stick/server/main.go

func process(conn net.Conn) {
    defer conn.Close()
    reader := bufio.NewReader(conn)
    var buf [1024]byte
    for {
        n, err := reader.Read(buf[:])
        if err == io.EOF {
            break
        }
        if err != nil {
            fmt.Println("read from client failed, err:", err)
            break
        }
        recvStr := string(buf[:n])
        fmt.Println("收到client发来的数据:", recvStr)
    }
}

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

客户端代码如下:

// socket_stick/server/main.go

func process(conn net.Conn) {
    defer conn.Close()
    reader := bufio.NewReader(conn)
    var buf [1024]byte
    for {
        n, err := reader.Read(buf[:])
        if err == io.EOF {
            break
        }
        if err != nil {
            fmt.Println("read from client failed, err:", err)
            break
        }
        recvStr := string(buf[:n])
        fmt.Println("收到client发来的数据:", recvStr)
    }
}

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

将上面的代码保存后,分别编译。先启动服务端再启动客户端,可以看到服务端输出结果如下:

收到client发来的数据: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?Hello, Hello. How are you?

客户端分10次发送的数据,在服务端并没有成功的输出10次,而是多条数据“粘”到了一起。

1.1.1 为什么出现粘包

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

“粘包”可发生在发送端也可发生在接收端:

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

1.1.2 解决办法

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

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

我们可以自己定义一个协议,比如数据包的前4个字节为包头,里面存储的是发送的数据的长度。

// socket_stick/proto/proto.go
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
}

接下来在服务端和客户端分别使用上面定义的proto包的Decode和Encode函数处理数据。

服务端代码如下:

// socket_stick/server2/main.go

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

客户端代码如下:

// socket_stick/client2/main.go

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)
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Go语言网络编程(socket编程)TCP粘包 的相关文章

随机推荐

  • Python程序运行出现TypeError: run() missing 1 required positional argument: ‘test’

    TypeError run missing 1 required positional argument test 文章目录 源代码 错误类型 解决办法 源代码 import unittest from app import BASE DI
  • 积木画-蓝桥杯(dp)

    试题 G 积木画 问题描述 小明最近迷上了积木画 有这么两种类型的积木 分别为 I 型 大小为 2 个单位面积 和 L 型 大小为 3 个单位面积 同时 小明有一块面积大小为 2 N 的画布 画布由 2 N 个 1 1 区域构成 小明需要用
  • CSS布局-解决flex布局下多行元素既可以均匀对齐最后一行也可以从左到右排列

    首先 你有没有遇到过这种情况 你既想让它均匀分布 还想让最后一行从左向右排列 此时你的代码应该是这样的 box display flex justify content space evenly flex wrap wrap box inf
  • 在工作中学习,在学习中工作

    2011年八月第二周 时间还是和以前一样过的那么快 一周的时间转眼就过去了 这一周 对工作中所需的知识点进行了两天的培训 其余的时间都是Flex及其开源框架Caringorm的学习 因为之前没有接触过Flex 所以现在的学习是一种 即学式
  • 自定义注解注入属性值(基于类构造方法)

    本文将举例说明如何通过构造方法来处理注解 实现属性注入 自定义注解Name 实现超类 定义注解处理方法 子类使用注解 自定义注解Name 定义一个注解 Name Name注解可以接收一个String类型的属性 并且可以使用在类或者属性上 T
  • 机器学习——入门

    机器学习算法分类 监督学习 目标值是类别 gt 分类问题 k 近邻算法 贝叶斯分类 决策树与随机森林 逻辑回归 目标值是连续型的数据 gt 回归问题 线性回归 岭回归 无监督学习 没有目标值 gt 无监督学习 聚类k means 机器学习开
  • AD7124-4 精度

    AD7124芯片 是属于AD公司的较新产品 高达24位的精度 确实让人眼馋 究竟如何呢 寄存器配置顺序 1 AD上电后 先关闭SPI片选 2 使能开启单片机的 SPI 3 复位设备及所有内部寄存器 发送64位的1 也就是8个0xFF 4 读
  • C语言--weak的作用

    weak 顾名思义是 弱 的意思 在汇编中 在函数名称后面加 WEAK 来表示 而在 C语言中 在函数名称前面加上 weak 修饰符来表示 这样的函数我们称为 弱函数 被 WEAK 或 weak 声明的函数 我们可以在自己的文件中重新定义一
  • 获取当年、当月的开始结束日期

    import java time LocalDate import java time LocalDateTime import java time LocalTime import java time temporal TemporalA
  • QT 如何用多线程实现数据处理和界面显示刷新速度够快

    Qt 支持多线程编程 因此可以通过创建多个线程来实现数据处理和界面显示刷新的高效实现 一种常用的做法是 在一个线程中处理数据 另一个线程负责界面显示的更新 数据处理线程可以通过信号和槽的机制来通知界面显示线程更新界面 这种方法的好处是 数据
  • AI安全初探——利用深度学习检测DNS隐蔽通道

    AI安全初探 利用深度学习检测DNS隐蔽通道 目录 AI安全初探 利用深度学习检测DNS隐蔽通道 1 DNS 隐蔽通道简介 2 算法前的准备工作 数据采集 3 利用深度学习进行DNS隐蔽通道检测 4 验证XShell的检测效果 5 结语 1
  • 了解关于Hadoop的12个事实

    原文 http os 51cto com art 201206 345249 htm 了解关于Hadoop的12个事实 本文中 分析师给出了关于Hadoop的12点事实 帮助您认识一个真实的Apache Hadoop生态系统 作者 茶一峰
  • uni-app,解决方案, 已存在待跳转页面,请不要连续多次跳转页面问题

    问题的解决思路 设置全局变量flag 以及封装跳转函数 设置定时器不允许几秒钟重复跳转 1 如果采用 uni navigateTo 跳转 jumpFlag function path 跳转开关 if getApp globalData is
  • Arduino基础项目篇-基于Arduino的智能小车

    从这篇开始 后续会陆陆续续写一些自己入门单片机以来做过的一些项目教程 y由于不是现在做的 所以我可能没有调试的照片啥之类的 而且做的东西大多都拆了 我刚入门Arudino时 做的第一个项目 就是Arduino智能小车 做出来的小车具有红外避
  • 华大单片机KEIL报错_WEAK的解决方案

    1 Keil编译无法识别 WEAK时的问题清单如下 在使用Keil编译有时出现无法识别 WEAK的问题 截图如下 提示的错误信息如下 mcu common interrupts hc32l13x c 72 error 77 D this d
  • ACL技术-------访问控制列表

    1 ACL原理 设备根据事先设置好的报文匹配规则对经过该设备的流量进行匹配 然后对报文执行预先设定的处理动作 2 ACL功能 1 访问控制 在流量流入或者流出的接口上匹配流量 动作 允许 permit 拒绝 deny 2 抓取流量 3 AC
  • Error in py_run_file_impl(file, local, convert) : ModuleNotFoundError: No module named ‘igraph‘

    在HPC平台上跑我的R语言代码 结果一直报错说 Error in py run file impl file local convert ModuleNotFoundError No module named igraph 我就知道是我R语
  • 单片机--USART

    目录 1 通信的基础知识 2 USART 3 串口通信协议 4 相关寄存器 串口控制寄存器 波特率寄存器 中断和状态寄存器 编辑 数据发送寄存器 数据接收寄存器 5 USART功能框图 6 串口发送实验 实验要求 1 观察实物 2 分析原理
  • 路由与路由表简介

    路由的概念 从字面上来说 路由 就是路径选择的意思 路由是指网络设备通过网络将信息正确传输到指定目的地的方式 而路由器正是这样的 网络设备 它可以根据目标网络选择 最优 的路径来决定下一跳跳向哪个路由器 但是什么是最优的路径 最优并不意味着
  • Go语言网络编程(socket编程)TCP粘包

    1 TCP粘包 服务端代码如下 socket stick server main go func process conn net Conn defer conn Close reader bufio NewReader conn var