| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 网络协议 -> TCP协议-TCP粘包问题 -> 正文阅读 |
|
[网络协议]TCP协议-TCP粘包问题 |
一、前言??????? 我们知道,TCP是一个面向字节流的传输层协议。“流” 意味着 TCP 所传输的数据是没有边界的。这不同于 UDP 协议提供的是面向消息的传输服务,其传输的数据是有边界的。TCP 的发送方无法保证对方每次收到的都是一个完整的数据包。于是就有了粘包、拆包问题的出现。粘包、拆包问题只发生在TCP协议中。 二、什么是粘包、拆包?假设客户端向服务器连续发送了两个数据包,用 packet1 和 packet2 来表示,那么服务端收到的数据可以分为下面三种情况:
三、粘包产生原因及解决办法3.1 TCP粘包产生原因TCP 发生粘包、拆包问题,主要是由于以下几个原因: (1)应用进程写入的数据量大于TCP发送缓冲区的大小,这将会发生拆包。应用进程调用write()系统调用,将应用层的缓冲区中的数据拷贝到TCP socket 的发送缓冲区。而TCP的发送缓冲区有一个 SO_SNDBUF 的大小限制,如果应用层的缓冲区数据大小大于TCP发送缓冲区的大小,则数据包需要进行拆包处理,分多次进行发送。 (2)应用进程写入的数据量小于TCP发送缓冲区的大小,这将会发生粘包。应用进程调用write()系统调用,将应用层的缓冲区中的数据拷贝到TCP socket 的发送缓冲区。由于TCP发送缓存空间足够,它会等到有多个数据包时,再组装成一个TCP报文段,然后通过网卡发送到网络中去。 (3)当应用进程发送的数据包 packet 大于 MSS(最大报文段长度)时,将会发生拆包。TCP在传送数据时,是以报文段为单位发送数据的,而待发送的数据是以 MSS 为单位进行分割的。如果应用进程发送的数据包packet的长度大于 MSS,必然需要对该数据包进行拆包处理。 ??????? 以上这几种情况,都会导致一个完整的应用层数据包被分割成多片发送出去,从而使接收方不是按完整的数据包方式来接收数据。
3.2 粘包问题的解决办法粘包问题的最本质原因在于接收方无法分辨出消息与消息之间的边界在哪?我们通过使用某种方法给出边界,常用的方法有以下几种: (1)发送定长包。即发送端将每个数据包封装为固定长度(长度不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。 (2)包头加上包体长度。发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便可以知道每一个数据包的实际长度了。(此为常用方法) (3)在包尾部设置边界标记。发送端在每个数据包尾部添加边界标记,可以使用特殊符号作为边界标记。如此,接收端通过这个边界标记就可以将不同的数据包拆分开来。但这可能会存在一个问题:如果数据包内容中也包含有边界标记,则会被误判为消息的边界,导致出错。这样方法要视具体情况而定。例如,FTP协议就是采用 "\r\n" 来识别一个消息的边界的。 四、粘包解决方案—代码实现4.1 粘包解决方案1:使用定长包这里需要实现对 read()、write() 系统调用的封装,函数声明如下:
这两个封装函数的参数列表和返回值与 read、write 一致。它们的作用是读取 / 写入 count 个字节后再返回。代码实现如下:
有了这两个封装函数,我们就可以使用定长包来发送/接收数据了。示例代码如下:
程序分析:每个消息体都是以固定的 512 字节长度来发送,以此来区分每一个消息,这就是用定长包的方法解决粘包的问题。 缺点:定长包解决方案的缺点在于会增加网络的负担,无论每次发送的有效数据是多大,都得按照固定的数据长度进行发送。 4.2 粘包解决方案2:使用结构体,显式说明数据部分的长度在这个方案中,我们需要定义一个消息结构体,结构体中指明包体中数据部分的长度,用4个字节的无符号整数来表示。当接收端收到消息后,先读取前4个字节,获取消息的长度,然后根据消息长度进行数据的读取。定义的结构体如下:
读写过程如下所示,这里只给出关键代码进行说明:
下面是读取数据的过程,先读取4个字节长度,获取msgLen字段的值,该字段指示了有效数据的长度,然后依据该字段给出长度再读取data部分。
4.3 粘包解决方案3:按行读取FTP 协议就是采用 "\r\n" 标记来识别一个消息的边界的。我们这里实现一个按行读取的函数,该函数能够按 '\n' 来识别消息的边界。这里先介绍一个函数:
与 read 函数相比,recv 函数的区别在于两点: 1. recv 函数只能用于用于socket IO。 2. recv 函数含义 flags 参数,可以指定一些选项。 recv 函数的 flags 参数常用的选项有:
????????为了实现按行读取,我们需要使用 recv 函数的 MSG_PEEK 选项。PEEK 本意是“偷看、窥视”,我们可以理解为“窥视数据”,看看 socket 的缓冲区是否有某种数据,但不清除缓冲区中的内容。
下面的函数实现了按行读取的功能,代码如下:
参考 |
|
网络协议 最新文章 |
使用Easyswoole 搭建简单的Websoket服务 |
常见的数据通信方式有哪些? |
Openssl 1024bit RSA算法---公私钥获取和处 |
HTTPS协议的密钥交换流程 |
《小白WEB安全入门》03. 漏洞篇 |
HttpRunner4.x 安装与使用 |
2021-07-04 |
手写RPC学习笔记 |
K8S高可用版本部署 |
mySQL计算IP地址范围 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 | -2024/12/28 20:18:04- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |
数据统计 |