当前位置: 首页 > news >正文

tcp粘包原理和解决

tcp粘包原理和解决

​ 咱们先通过展示基于tcp 的cs端消息通信时的现象,带着问题再解释下面的tcp粘包问题。

一、原始代码

tcp 服务端代码

// socket_stick/server/main.gofunc process(conn net.Conn) {defer conn.Close()reader := bufio.NewReader(conn)var buf [1024]bytefor {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)}
}

tcp客户端代码

// socket_stick/client/main.gofunc 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?`conn.Write([]byte(msg))}
}

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

收到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?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?Hello, Hello. How are you?Hello, Hello. How are you?

二、粘包解析

1.为什么会出现TCP粘包问题

TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾,出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。

在socket网络编程中,都是端到端通信,由客户端端口+服务端端口+客户端IP+服务端IP+传输协议组成的五元组可以明确的标识一条连接。在TCP的socket编程中,发送端和接收端都有成对的socket。发送端为了将多个发往接收端的包,更加高效的的发给接收端,于是采用了优化算法(Nagle算法),将多次间隔较小、数据量较小的数据,合并成一个数据量大的数据块,然后进行封包。那么这样一来,接收端就必须使用高效科学的拆包机制来分辨这些数据。

2.造成TCP粘包原因

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

3.什么时候需要处理粘包问题

  1. 如果发送方发送的多组数据本来就是同一块数据的不同部分,比如说一个文件被分成多个部分发送,这时当然不需要处理粘包现象
  2. 如果多个分组毫不相干,甚至是并列关系,那么这个时候就一定要处理粘包现象了

4.如何处理粘包问题

1)发送方

对于发送方造成的粘包问题,可以通过关闭Nagle算法来解决,使用TCP_NODELAY选项来关闭算法。

(2)接收方

接收方没有办法来处理粘包现象,只能将问题交给应用层来处理。

(2)应用层

应用层的解决办法简单可行,不仅能解决接收方的粘包问题,还可以解决发送方的粘包问题。

解决办法:循环处理,应用程序从接收缓存中读取分组时,读完一条数据,就应该循环读取下一条数据,直到所有数据都被处理完成,但是如何判断每条数据的长度呢?

  1. 格式化数据:每条数据有固定的格式(开始符,结束符),这种方法简单易行,但是选择开始符和结束符时一定要确保每条数据的内部不包含开始符和结束符。
  2. 发送长度:发送每条数据时,将数据的长度一并发送,例如规定数据的前4位是数据的长度,应用层在处理时可以根据长度来判断每个分组的开始和结束位置。

5.UDP会不会产生粘包问题

TCP为了保证可靠传输并减少额外的开销(每次发包都要验证),采用了基于流的传输,基于流的传输不认为消息是一条一条的,是无保护消息边界的(保护消息边界:指传输协议把数据当做一条独立的消息在网上传输,接收端一次只能接受一条独立的消息)。

UDP则是面向消息传输的,是有保护消息边界的,接收方一次只接受一条独立的信息,所以不存在粘包问题。

举个例子:有三个数据包,大小分别为2k、4k、6k,如果采用UDP发送的话,不管接受方的接收缓存有多大,我们必须要进行至少三次以上的发送才能把数据包发送完,但是使用TCP协议发送的话,我们只需要接受方的接收缓存有12k的大小,就可以一次把这3个数据包全部发送完毕。

三、改进代码

解决思路:更改发送方可控性不高,所以需要在应用侧入手,而且不更改协议继续基于tcp实现。

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

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

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

// socket_stick/proto/proto.go
package protoimport ("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 int32err := 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
}

tcp 服务端使用decode解码消息

// socket_stick/server2/main.gofunc 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)}
}

tcp 客户端使用encode编码消息

// socket_stick/client2/main.gofunc 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)}
}
改进后发包效果:
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?

相关文章:

tcp粘包原理和解决

tcp粘包原理和解决 ​ 咱们先通过展示基于tcp 的cs端消息通信时的现象&#xff0c;带着问题再解释下面的tcp粘包问题。 一、原始代码 tcp 服务端代码 // socket_stick/server/main.gofunc process(conn net.Conn) {defer conn.Close()reader : bufio.NewReader(conn)var bu…...

C语言预处理艺术:编译前的魔法之旅

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文一、预处理的作用与流程&#xf…...

C++算法第十六天

本篇文章我们继续学习动态规划 第一题 题目链接 978. 最长湍流子数组 - 力扣&#xff08;LeetCode&#xff09; 题目解析 从上图可见其实有三个状态 代码原理 注意&#xff1a;我们在分析题目的时候分析出来的是三个状态&#xff0c;分别是上升、下降、平坦&#xff0c;但是…...

计算机网络 (45)动态主机配置协议DHCP

前言 计算机网络中的动态主机配置协议&#xff08;DHCP&#xff0c;Dynamic Host Configuration Protocol&#xff09;是一种网络管理协议&#xff0c;主要用于自动分配IP地址和其他网络配置参数给连接到网络的设备。 一、基本概念 定义&#xff1a;DHCP是一种网络协议&#xf…...

归子莫的科技周刊#2:白天搬砖,夜里读诗

归子莫的科技周刊#2&#xff1a;白天搬砖&#xff0c;夜里读诗 本周刊开源&#xff0c;欢迎投稿。 刊期&#xff1a;2025.1.5 - 2025.1.11。原文地址。 封面图 下班在深圳看到的夕阳&#xff0c;能遇到是一种偶然的机会&#xff0c;能拍下更是一种幸运。 白天搬砖&#xff0c;…...

平滑算法 效果比较

目录 高斯平滑 效果对比 移动平均效果比较: 高斯平滑 效果对比 右边两个参数是1.5 2 代码: smooth_demo.py import numpy as np import cv2 from scipy.ndimage import gaussian_filter1ddef gaussian_smooth_array(arr, sigma):smoothed_arr = gaussian_filter1d(arr, s…...

Elasticsearch容器启动报错:AccessDeniedException[/usr/share/elasticsearch/data/nodes];

AccessDeniedException 表明 Elasticsearch 容器无法访问或写入数据目录 /usr/share/elasticsearch/data/nodes。这是一个权限问题。 问题原因&#xff1a; 1、宿主机目录权限不足&#xff1a;映射到容器的数据目录 /data/es/data 在宿主机上可能没有足够的权限供容器访问。 …...

【Linux系统编程】——深入理解 GCC/G++ 编译过程及常用选项详解

文章目录 1. GCC/G 编译过程预处理&#xff08;Preprocessing&#xff09;编译&#xff08;Compilation&#xff09;汇编&#xff08;Assembly&#xff09;连接&#xff08;Linking&#xff09; 静态链接与动态链接静态链接动态链接静态库和动态库 GCC 常用选项关于编译器的周边…...

Mac安装配置使用nginx的一系列问题

brew安装nginx https://juejin.cn/post/6986190222241464350 使用brew安装nginx&#xff0c;如下命令所示&#xff1a; brew install nginx 如下图所示&#xff1a; 2.查看nginx的配置信息&#xff0c;如下命令&#xff1a; brew info nginxFrom:xxx 这样的&#xff0c;是n…...

Vue3中使用组合式API通过路由传值详解

在Vue 3中&#xff0c;使用组合式API来传递路由参数是一种常见的需求。Vue Router 是 Vue.js 的官方路由管理工具&#xff0c;可以在不同的场景下通过多种方式传递和接收路由参数。下面将详细讲解几种常见的路由传值方式&#xff0c;并提供相应的代码示例。 1. 通过路由参数传…...

两分钟解决 :![rejected] master -> master (fetch first) , 无法正常push到远端库

目录 分析问题的原因解决 分析问题的原因 在git push的时候莫名遇到这种情况 若你在git上修改了如README.md的文件。由于本地是没有README.md文件的&#xff0c;所以导致 远端仓库git和本地不同步。 将远端、本地进行合并就可以很好的解决这个问题 注意&#xff1a;直接git pu…...

浏览器安全(同源策略及浏览器沙箱)

一、同源策略&#xff08;Same Origin Policy&#xff09; 1.定义 同源策略&#xff08;Same - origin Policy&#xff09;是一种浏览器的安全机制。它规定一个网页的脚本只能访问和操作与它同源的资源。这里的 “源” 包括协议&#xff08;如 http、https&#xff09;、域名&…...

w~Transformer~合集11

我自己的原文哦~ https://blog.51cto.com/whaosoft/12472192 #LightSeq 最高加速9倍&#xff01;字节跳动开源8比特混合精度Transformer引擎,近年来&#xff0c;Transformer 已经成为了 NLP 和 CV 等领域的主流模型&#xff0c;但庞大的模型参数限制了它的高效训练和推理。…...

Coursera四门课备考入学考试

某学校入学考试复习用&#xff0c;刷到的话纯靠缘分&#xff0c;不方便回答多余问题 &#xff08;博主本人waive掉了没有考过&#xff0c;但还是基本都学完了&#xff09; 记录学习coursera的四门课&#xff08;顺序Py在DS前&#xff0c;其他无所谓&#xff09; Mathematics fo…...

Flink(八):DataStream API (五) Join

1. Window Join Window join 作用在两个流中有相同 key 且处于相同窗口的元素上。这些窗口可以通过 window assigner 定义&#xff0c;并且两个流中的元素都会被用于计算窗口的结果。两个流中的元素在组合之后&#xff0c;会被传递给用户定义的 JoinFunction 或 FlatJoinFunct…...

HarmonyOS NEXT边学边玩:从零实现一个影视App(六、视频播放页的实现)

在HarmonyOS NEXT中&#xff0c;ArkUI是一个非常强大的UI框架&#xff0c;能够帮助开发者快速构建出美观且功能丰富的用户界面。本文将详细介绍如何使用ArkUI实现一个影视App的视频播放页面。将从零开始&#xff0c;逐步构建一个功能完善的视频播放页面&#xff0c;并解释每一部…...

salesforce实现一个字段的默认初始值根据另一个字段的值来自动确定

在 Salesforce 中&#xff0c;可以通过 公式字段 或 触发器 (Trigger) 实现字段的默认初始值根据另一个字段的值来自动确定&#xff0c;具体实现方法如下&#xff1a; 1. 使用公式字段 公式字段是一种动态字段&#xff0c;值会根据公式实时计算。 步骤&#xff1a; 导航到字段…...

Linux 文件权限详解

目录 前言 查看文件权限 修改文件权限 符号方式 数字方式 前言 Linux 文件权限是系统中非常重要的概念之一&#xff0c;用于控制对文件和目录的访问。权限分为读&#xff08;Read&#xff09;、写&#xff08;Write&#xff09;、执行&#xff08;Execute&#xff09;三个…...

【混合开发】CefSharp+Vue桌面应用程序开发

为什么选择CefSharpVue做桌面应用程序 CefSharp 基于 Chromium Embedded Framework (CEF) &#xff0c;它可以将 Chromium 浏览器的功能嵌入到 .NET 应用程序中。通过 CefSharp&#xff0c;开发者可以在桌面应用程序中集成 Web 技术&#xff0c;包括 HTML、JavaScript、CSS 等…...

springBoot项目使用Elasticsearch教程

目录 一、引言&#xff08;一&#xff09;使用背景&#xff08;二&#xff09;版本库区别 二、引入依赖&#xff08;一&#xff09;springboot集成的es依赖&#xff08;建议&#xff09;&#xff08;二&#xff09;es提供的客户端库 三、配置&#xff08;以yaml文件为例&#x…...

模型 多元化思维(系统科学)

系列文章分享模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。融合多学科知识&#xff0c;全面解决问题。 1 多元化思维模型的应用 1.1 完美日记的私域流量运营 完美日记作为美妆行业的新兴品牌&#xff0c;通过多元化的思维模型在私域流量运营中取得了显著成功。…...

Google地图瓦片爬虫

地图地址说明 1、谷歌矢量(中文标注) http://mt{0-3}.google.cn/vt/vm416115521&hlzh-CN&glcn&x{x}&y{y}&z{z}&sGalileo 2、谷歌矢量(英文标注) http://mt{0-3}.google.cn/vt/vm416115521&hlen&glcn&x{x}&y{y}&z{z}&sGali…...

【C++】size_t全面解析与深入拓展

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;一、什么是size_t&#xff1f;为什么需要size_t&#xff1f; &#x1f4af;二、size_t的特性与用途1. size_t是无符号类型示例&#xff1a; 2. size_t的跨平台适应性示例对…...

Web端实时播放RTSP视频流(监控)

一、安装ffmpeg: 1、官网下载FFmpeg: Download FFmpeg 2、点击Windows图标,选第一个:Windows builds from gyan.dev 3、跳转到下载页面: 4、下载后放到合适的位置,不用安装,解压即可: 5、配置path 复制解压后的\bin路径,配置环境变量如图: <...

学习 Git 的工作原理,而不仅仅是命令

Git 是常用的去中心化源代码存储库。它是由 Linux 创建者 Linus Torvalds 创建的&#xff0c;用于管理 Linux 内核源代码。像 GitHub 这样的整个服务都是基于它的。因此&#xff0c;如果您想在 Linux 世界中进行编程或将 IBM 的 DevOps Services 与 Git 结合使用&#xff0c;那…...

C语言变长嵌套数组常量初始化定义技巧

有时候&#xff0c;我们需要在代码里配置一些常量结构&#xff0c;比如一个固定的动作流程ActionFlow&#xff1a;包含N&#xff08;即flow_num&#xff09;个动作列表&#xff08;ActionArray&#xff09;&#xff0c;每个动作列表包含M&#xff08;即act_num&#xff09;个可…...

如何查看特定版本的Spring源码

写在前面&#xff1a;大家好&#xff01;我是晴空๓。如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正&#xff0c;感谢大家的不吝赐教。我的唯一博客更新地址是&#xff1a;https://ac-fun.blog.csdn.net/。非常感谢大家的支持。一起加油&#xff0c;冲鸭&#x…...

【深度学习】关键技术-激活函数(Activation Functions)

激活函数&#xff08;Activation Functions&#xff09; 激活函数是神经网络的重要组成部分&#xff0c;它的作用是将神经元的输入信号映射到输出信号&#xff0c;同时引入非线性特性&#xff0c;使神经网络能够处理复杂问题。以下是常见激活函数的种类、公式、图形特点及其应…...

网关相关知识

文章目录 什么是网关网关的主要作用网关的运用 什么是网关 网关又称网间连接器、协议转换器&#xff0c;也就是网段(局域网、广域网)关卡&#xff0c;不同网段中的主机不能直接通信&#xff0c;需要通过关卡才能进行互访&#xff0c;比如IP地址为192.168.31.9(子网掩码&#x…...

SpringBoot整合SpringSecurity详解

文章目录 SpringBoot整合SpringSecurity详解一、引言二、引入依赖三、配置 Spring Security四、自定义用户详细信息服务五、使用示例1. 创建用户实体类2. 测试登录功能 六、总结 SpringBoot整合SpringSecurity详解 一、引言 在当今的软件开发中&#xff0c;安全是一个至关重要…...