TCP粘包、拆包与解决方案、C++ 实现
说明:
TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。即面向流的通信是无消息保护边界的。
TCP粘包、拆包图解
假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到字节数是不确定的,故可能存在以下四种情况:
- 服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包
- 服务端一次接受到了两个数据包,D1和D2粘合在一起,称之为TCP粘包
- 服务端分两次读取到了数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次 读取到了D2包的剩余内容,这称之为TCP拆包
- 服务端分两次读取到了数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余部分内容D1_2和完整的D2包。
发生原因:
- socket缓冲区与滑动窗口
- MSS/MTU限制
- Nagle算法
解决方案:定义通信协议
通过定义应用的协议(protocol)来解决。协议的作用就定义传输数据的格式。这样在接受到的数据的时候,如果粘包了,就可以根据这个格式来区分不同的包,如果拆包了,就等待数据可以构成一个完整的消息来处理。目前业界主流的协议(protocol)方案可以归纳如下:
定长协议:
假设我们规定每3个字节,表示一个有效报文,如果我们分4次总共发送以下9个字节:
+---+----+------+----+| A | BC | DEFG | HI |+---+----+------+----+
那么根据协议,我们可以判断出来,这里包含了3个有效的请求报文
+-----+-----+-----+| ABC | DEF | GHI |+-----+-----+-----+
特殊字符分隔符协议
在包尾部增加回车或者空格符等特殊字符进行分割
例如,按行解析,遇到字符\n、\r\n的时候,就认为是一个完整的数据包。对于以下二进制字节流:
+--------------+| ABC\nDEF\r\n |+--------------+
那么根据协议,我们可以判断出来,这里包含了2个有效的请求报文
+-----+-----+| ABC | DEF |+-----+-----+
长度编码:
将消息分为消息头和消息体,消息头中用一个int型数据(4字节),表示消息体长度的字段。在解析时,先读取内容长度Length,其值为实际消息体内容(Content)占用的字节数,之后必须读取到这么多字节的内容,才认为是一个完整的数据报文。
header body
+--------+----------+
| Length | Content |
+--------+----------+
长度编码方案C++ 实现:
关于数据包的包头大小可以根据自己的实际需求进行设定,这里没有啥特殊需求,因此规定包头的固定大小为4个字节,用于存储当前数据块的总字节数。
发送端:
数据的发送分为 4 步:
- 根据待发送的数据长度 N 动态申请一块固定大小的内存:N+4(4 是包头占用的字节数)
- 将待发送数据的总长度写入申请的内存的前四个字节中,此处需要将其转换为网络字节序(大端)
- 将待发送的数据拷贝到包头后边的地址空间中,将完整的数据包发送出去(字符串没有字节序问题)
- 释放申请的堆内存。
/*
函数描述: 发送指定的字节数
函数参数:- fd: 通信的文件描述符(套接字)- msg: 待发送的原始数据- size: 待发送的原始数据的总字节数
函数返回值: 函数调用成功返回发送的字节数, 发送失败返回-1
*/
int writen(int fd, const char* msg, int size)
{const char* buf = msg;int count = size;while (count > 0){int len = send(fd, buf, count, 0);if (len == -1){close(fd);return -1;}else if (len == 0){continue;}buf += len;count -= len;}return size;
}/*
函数描述: 发送带有数据头的数据包
函数参数:- cfd: 通信的文件描述符(套接字)- msg: 待发送的原始数据- len: 待发送的原始数据的总字节数
函数返回值: 函数调用成功返回发送的字节数, 发送失败返回-1
*/
int sendMsg(int cfd, char* msg, int len)
{if(msg == NULL || len <= 0 || cfd <=0){return -1;}// 申请内存空间: 数据长度 + 包头4字节(存储数据长度)char* data = (char*)malloc(len+4);int bigLen = htonl(len);memcpy(data, &bigLen, 4);memcpy(data+4, msg, len);// 发送数据int ret = writen(cfd, data, len+4);// 释放内存free(data);return ret;
}
接收端:
- 首先接收 4 字节数据,并将其从网络字节序转换为主机字节序,这样就得到了即将要接收的数据的总长度
- 根据得到的长度申请固定大小的堆内存,用于存储待接收的数据
- 根据得到的数据块长度接收固定数目的数据保存到申请的堆内存中
- 处理接收的数据
- 释放存储数据的堆内存
/*
函数描述: 接收指定的字节数
函数参数:- fd: 通信的文件描述符(套接字)- buf: 存储待接收数据的内存的起始地址- size: 指定要接收的字节数
函数返回值: 函数调用成功返回发送的字节数, 发送失败返回-1
*/
int readn(int fd, char* buf, int size)
{char* pt = buf;int count = size;while (count > 0){int len = recv(fd, pt, count, 0);if (len == -1){return -1;}else if (len == 0){return size - count;}pt += len;count -= len;}return size;
}/*
函数描述: 接收带数据头的数据包
函数参数:- cfd: 通信的文件描述符(套接字)- msg: 一级指针的地址,函数内部会给这个指针分配内存,用于存储待接收的数据,这块内存需要使用者释放
函数返回值: 函数调用成功返回接收的字节数, 发送失败返回-1
*/
int recvMsg(int cfd, char** msg)
{// 接收数据// 1. 读数据头int len = 0;readn(cfd, (char*)&len, 4);len = ntohl(len);printf("数据块大小: %d\n", len);// 根据读出的长度分配内存,+1 -> 这个字节存储\0char *buf = (char*)malloc(len+1);int ret = readn(cfd, buf, len);if(ret != len){close(cfd);free(buf);return -1;}buf[len] = '\0';*msg = buf;return ret;
}
这样,在进行套接字通信的时候通过调用封装的 sendMsg() 和 recvMsg() 就可以发送和接收带数据头的数据包了,而且完美地解决了粘包的问题。
TCP粘包、拆包与解决方案、C++ 实现相关推荐
- TCP粘包|拆包和解决方案
1 产生原因 TCP是面向连接的,面向流的,提供高可靠性服务.收发两端(客户端和服务端)都要有一一成对的socket,因此,发送端为了将多个发给接收端的包,更有效的发给对方,使用了优化算法(Nagle ...
- Netty4实战 - TCP粘包拆包解决方案
Netty4实战 - TCP粘包&拆包解决方案 参考文章: (1)Netty4实战 - TCP粘包&拆包解决方案 (2)https://www.cnblogs.com/hunrry/p ...
- 《精通并发与Netty》学习笔记(13 - 解决TCP粘包拆包(一)概念及实例演示)
一.粘包/拆包概念 TCP是一个"流"协议,所谓流,就是没有界限的一长串二进制数据.TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的 ...
- Netty(二)——TCP粘包/拆包
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7814644.html 前面讲到:Netty(一)--Netty入门程序 主要内容: TCP粘包/拆包的基础知 ...
- java tcp怎么拆包_Java网络编程基础之TCP粘包拆包
TCP是个"流"协议,所谓流,就是没有界限的一串数据.大家可以想象河里的流水,他们是连成一片的,其间并没有分界线.TCP底层并不了解上层业务数据的具体含义,他会根据TCP缓冲区的实 ...
- TCP——粘包/拆包
TCP粘包/拆包 TCP是个"流"协议,所谓流,就是没有界限的一串数据.大家可以想想河里的流水,它们是连成一片的,其间并没有分界线.TCP底层并不了解上层业务数据的具体含义,它会根 ...
- TCP粘包/拆包问题
目录 TCP粘包/拆包 TCP粘包/拆包问题说明 TCP粘包/拆包发生的原因 粘包问题的解决策略 未考虑TCP粘包导致功能异常案例 TimeServer的改造 TimeClient的改造 利用Lin ...
- netty解决TCP粘包/拆包导致的半包读写问题的三种方案
解决方案一:LineBasedFrameDecoder+StringDecoder来解决TCP的粘包/拆包问题 只需要在客户端和服务端加上45.46两行代码并且在发送消息的时候加上换行符即可解决TCP ...
- Netty权威指南(四)TCP粘包/拆包问题
TCP粘包/拆包问题解决之道 上一章 一.介绍 1.1 TCP粘包/拆包问题说明 1.2 TCP粘包/拆包发生的原因 1.3 粘包问题的解决策略 二.未考虑TCP粘包导致的功能异常案例 2.1 Tim ...
- java获取一个tcp包大小_Java网络编程之TCP粘包拆包
TCP是个"流"协议,所谓流,就是没有界限的一串数据.大家可以想象河里的流水,他们是连成一片的,其间并没有分界线.TCP底层并不了解上层业务数据的具体含义,他会根据TCP缓冲区的实 ...
最新文章
- A - Wireless Network POJ - 2236
- 严蔚敏版《数据结构 (C语言版)》和《数据结构题集》(一)
- 一文说通Blazor for Server-Side的项目结构
- Flask-SQLALchemy 连接数据库
- sql join中能否使用case when_SQL(五)——多表查询
- html底部弹出选择,jQuery手机端底部弹出菜单列表特效代码
- PHP06 流程控制
- 关于mssql的学习体会,仅供参考!
- 多源最短路(Floyd算法)
- 单点登录原理以及简单实现
- 【20保研】天津大学智能与计算学部2020级研究生招生夏令营活动通知
- 经典的损人的话 (不带一个脏字,够狠毒)
- dw在html中删除css样式表,DW里CSS的详细介绍
- 各大搜索引擎网站登录入口大全
- js刻度尺插件_js滑块刻度尺插件
- 5 坐标变换与视觉测量
- java网络编程技术学习笔记(b站【狂神说Java】网络编程实战讲解)
- 【设计师必学】在SketchUp中Enscape的灯光照明技巧
- php 计算时差,php 计算时区的时差的简单示例
- select 取消下拉框倒小三角形样式
热门文章
- 新闻资讯门户类网站源码 织梦dedecms内核
- Unity:使用Catmull-Rom曲线创建道路模型
- 锐捷三层交换机route-map设置
- AVL Cruise和MATLAB DLL联合仿真时快速生成Simulink模型的方法
- 重生后发现高冷女同桌暗恋我!(一)
- 一个标准的行业分析怎么做
- php 框架的作用,ThinkPHP框架作用
- 可变长度操作码(扩展操作码)
- 概率论与数理统计复习全集(考研/期末复习)
- 硬盘检测软件测试培训,认识专业的考机工具PassMark BurnInTest_软件测试_软件测试培训_软件测试频道_中国IT实验室...