C++ Qt TCP协议,处理粘包、拆包问题,加上数据头来处理

目录

前言:

场景:

原因:

解决:

方案2具体细节:

纯C++服务端处理如下:

Qt客户端处理如下:


前言:

        tcp协议里面,除了心跳检测是关于长连接操作的处理,这个在前一篇已经提到过了,这一篇将会对tcp本身的一个问题,进行处理:那就是做网络通信大概率会遇到的问题,粘包、拆包问题,碰到这类问题对于新手来说都是比较棘手的,需要好好处理一下。

场景:

        使用tcp协议的时候:

        1、我明明发单个小包,都很正常呀,没啥问题呀,怎么我对单个小包多发几次,频率快一些,就会数据错乱了;

        2、我明明发小包都好着,怎么发打包就不行了,很奇怪呢?

        其实这2个场景你用抓包工具一抓,分析一下封包内容,就会一目了然。

原因:

        最本质的原因是tcp协议发送数据的时候,是不会告诉对方当前发送的数据包有多大的,这是协议头决定的,如果接收端用一个很大的缓冲区来接收发送端的小包的时候,就有可能一下子接收到多个小包,从而导致粘包现象;如果用一个较小的缓冲区来接收发送端的较大的数据包,也会导致一个包收不完,得分好多次才能接收完,导致拆包现象;

解决:

        要解决粘包或者拆包问题, 有如下几个方案;

        方案1:可以给数据包前后加上一些特殊标识,用来区分头尾,这个方案的缺陷是必须找好特殊标识,万一数据内容也包含特殊标识,就会导致解包错误;

        方案2:可以给数据包加上一个数据头,在数据头里面加上一个长度信息来表示整个封包的长度,通过长度来进行收包和解包;这个方案的操作是把整个包看成两部分,数据头+数据体;先收数据头,取出长度来开辟指定长度的缓冲区,先把数据头拷贝到缓冲区,接着再把剩余的数据体内容接收到缓冲区剩余区域里即可。

        方案3:可以把方案1、方案2结合起来,不过方案还是过于复杂,但也相对安全。

下面就以方案2来进行代码演示:

方案2具体细节:

        1、由于是C++实现的,可以给应用层封装一个私有协议,那就使用结构体来作为私有协议。

结构体声明如下:

enum TypeInfo
{HEART_CHECK_REQ, // 心跳检测请求HEART_CHECK_RES, // 心跳检测响应LOGIN_REQ, // 登录请求LOGIN_RES, // 登录响应UPLOAD_REQ, // 文件上传请求UPLOAD_RES, // 文本上传响应
};struct Head
{int flag;int type;int len;
};struct HeartCheckReq
{Head head;HeartCheckReq(){head.flag = 0; // 0 表示软件客户端,1表示硬件客户端head.type = HEART_CHECK_REQ;head.len = sizeof(HeartCheckReq);}
};struct HeartCheckRes
{Head head;HeartCheckRes() {head.flag = 0;head.type = HEART_CHECK_RES;head.len = sizeof(HeartCheckRes);}
};

纯C++服务端处理如下:

收包线程函数代码:

void ServerSocket::recvAndSendThread(SOCKET client)
{// 循环收发包,保存长连接通信while (true){/*char buffer[1024] = { 0 };int len_recv = recv(client, buffer, sizeof(buffer), 0);cout << "len_recv:" << len_recv << endl;if (len_recv <= 0) {cout << "socket收包异常:" << WSAGetLastError() << endl;break;}*/// 解决粘包或拆包问题char *head_buffer = new char[sizeof(Head)];int len_recv = recv(client, head_buffer, sizeof(Head), 0);int head_rest = sizeof(Head) - len_recv;while (head_rest > 0) { // 还有没收完的len_recv = recv(client, head_buffer+ (sizeof(Head) - head_rest),head_rest , 0);head_rest -= len_recv;}// 表示结构体头收完了,可以拿出总长度int len_total = ((Head*)head_buffer)->len;char *buffer = new char[len_total];memcpy(buffer, head_buffer, sizeof(Head)); // 先把数据头拷贝进去int len_rest = len_total - sizeof(Head); // 算出剩余长度while (len_rest > 0) {len_recv = recv(client, buffer + (len_total - len_rest), len_rest, 0);len_rest -= len_recv;}// 正常m_clientSockets[client] = HEART_CHECK_TIMES; // 重置心跳阈值cout << "buffer:" << buffer << endl;int type = ((Head*)buffer)->type;if (type == 100) {Data *d = (Data*)buffer;cout << "收到内容:" << d->data << endl;}else if (type == HEART_CHECK_REQ) {// 收到心跳包// 回一个响应包cout << "收到心跳请求包" << endl;HeartCheckRes res;send(client, (char*)&res, res.head.len, 0);}else if (type == UPLOAD_REQ) {// 上传版本文件cout << "收到版本管理上传文件包" << endl;UploadFileReq *req = (UploadFileReq*)buffer;cout << req->file_info.file_name << " md5:" << req->file_info.md5 << " size:" << req->file_info.file_size << endl<< " old:" << req->file_info.old_version << endl;// 服务端的业务:将文件写到指定目录,并且在数据库中记录相应的信息// 保存了之后,回一个响应包给客户端UploadFileManager upload;upload.business(client, req);}// 将收到的数据原封不动的回给客户端// send(client, buffer, len_recv, 0);// 释放内存,防止内存泄露delete[] head_buffer;delete[] buffer;head_buffer = nullptr;buffer = nullptr;}closesocket(client); // 关闭客户端套接字
}

Qt客户端处理如下:

收包槽函数代码:

        这里要注意的是,Qt的网络通信是异步的,不能像纯windows服务端那样,使用recv来阻塞收包,所以采用了一个全局变量来存储数据包,当然也可以考虑使用静态局部变量或者成员变量来处理,本文为了表示得更加直白,选择使用了全局变量 g_allBuffer。

QByteArray g_allBuffer; // 全局缓冲区,用来保存收到的封包内容
void TcpMainWindow::myRead()
{QByteArray buffer =  m_client->readAll();g_allBuffer.append(buffer);int len = g_allBuffer.size();while(len > 0){if(len < sizeof(Head)) break; // 不满足数据头大小,继续收包int len_total = ((Head*)(g_allBuffer.data()))->len;if(len < len_total) break; // 不满足全部大小,继续收包QByteArray datas = g_allBuffer.left(len_total);  // 可能会收到多个,先拿一个出来出来emit unpackSignal(datas);g_allBuffer = g_allBuffer.mid(len_total); // 处理完了,将后面的挪到前面来len = g_allBuffer.size();}
}

 解包业务槽代码如下:

void TcpMainWindow::unpackSlot(QByteArray buffer)
{QString buf = buffer;ui->label->setText(buf);m_heartCheckTimes = HEART_CHECK_TIMES; // 重置阈值int type = ((Head*)buffer.data())->type;if(type == 100){Data *d = (Data*)buffer.data();qDebug()<<"收到:"<<d->data;buf = d->data;ui->label->setText(buf);}else if(type == HEART_CHECK_RES){// 收到心跳响应包qDebug()<<"收到心跳响应包";}
}

最后,以上只提供了核心代码,哪里有不清楚的,可以留言谈论一些细节,也可以关注后私信给回复,可以发完整工程代码。

        

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/157563.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32通用定时器产生PWM信号

STM32通用定时器产生PWM信号 PWM信号stm32定时器PWM生成模式PWM配置基本步骤PWM周期计算CubeMX配置代码展现 本期内容我将展示使用STM32通用定时器产生PWM信号&#xff0c;这里以定时器3通道3为例 PWM信号 如果还不懂的话&#xff0c;可以看看 &#xff1a; “蓝桥杯单片机学习…

远程桌面访问MATLAB 2018B,提示License Manger Error -103,终极解决方案

通过远程桌面方位Windows Server系统下的MATLAB2018B&#xff0c;报错License Manger Error -103&#xff0c;Crack文件夹下的dll文件已经替换&#xff0c;同时也已经输出了lic文件&#xff0c;但是仍然无法打开。但是在本地桌面安装就没有问题。初步怀疑MATLAB的License使用机…

909-2014-T1

文章目录 1.原题2.算法思想3.关键代码4.完整代码5.运行结果 1.原题 为带表头的单链表类Chain编写一个成员函数Reverse&#xff0c;该函数对链表进行逆序操作&#xff08;将链表中的结点按与原序相反的顺序连接&#xff09;&#xff0c;要求逆序操作就地进行&#xff0c;不分配…

远程命令执行漏洞原理,以及防护绕过方式

一、背景 RCE(Remote Command /Code Execute) 远程代码执行漏洞 通过PHP代码注入、Java代码注入等方式连接程序中预留的后门或接口从而进行远程命令执行&#xff0c;达到对服务器的控制。 为什么会出现远程代码执行漏洞呢&#xff1f; Web应用有时需要调用执行一些系统命令函数…

数据库的基本操作

DDL&#xff1a; 1.查询 show databases; 创建数据库&#xff1a; create data 数据库名称; 创建数据库(判断&#xff0c;如果不存在则创建)&#xff1a; create database if not exists 数据库名称&#xff1b; 删除数据库 drop database 数据库名称&#xff1b; drop databa…

异行星低代码平台--第三方插件对接:企业微信平台对接(二)

异行星低代码平台可以集成企业微信&#xff0c;实现单点登录、消息推送和组织机构同步。 提示 此功能需要异行星低代码平台企业版授权才能使用。 企业微信集成​ 单点登录 异行星低代码平台集成到企业微信后&#xff0c;只要使用企业微信账户登录企业微信客户端&#xff0…

CSS-背景属性篇

属性名&#xff1a;background-color 功能&#xff1a;设置背景颜色 属性值&#xff1a;符合CSS中颜色规范的值 默认背景颜色是 transparent body{ background-color: blue; } 属性名&#xff1a;background-image 功能&#xff1a;设置背景图片 属性值&#xff1a;url(图片的…

sklearn中的TfidfTransformer和gensim中的TfidfModel的区别

sklearn.feature_extraction.text.TfidfTransformer 和 gensim.models.TfidfModel 都是用于计算文本数据的 TF-IDF 值的工具。它们的主要区别在于实现方式和输入数据的格式。 1、实现方式和输入数据格式&#xff1a; TfidfTransformer 是 scikit-learn 中的一个类&#xff0c;…

PyTorch多GPU训练时同步梯度是mean还是sum?

PyTorch 通过两种方式可以进行多GPU训练: DataParallel, DistributedDataParallel. 当使用DataParallel的时候, 梯度的计算结果和在单卡上跑是一样的, 对每个数据计算出来的梯度进行累加. 当使用DistributedDataParallel的时候, 每个卡单独计算梯度, 然后多卡的梯度再进行平均.…

shell 脚本语句

目录 条件语句 test 命令 比较整数数值 字符串比较 命令举 条件逻辑测试操作 组合写法 举例 双中括号 ​编辑 ( ) / { } if 语句的结构 case 语句 脚本举例 识别 yes 和 no 脚本 检查磁盘使用情况脚本 新建用户以及随机设置用户密码的脚本 补充命令 [RANDOM…

【高性能计算】CUDA,OpenCL,FPGA 加速,MPI

OpenCL OpenCL&#xff08;Open Computing Language&#xff09;是一种跨平台的GPU加速技术&#xff0c;由Khronos Group开发。OpenCL允许开发人员在不同的硬件平台上编写并行计算应用程序。 OpenCL使用C语言的子集来编写应用程序&#xff0c;并提供了一组API&#xff0c;可以…

web:[GXYCTF2019]禁止套娃

题目 打开页面显示为 没有其他信息&#xff0c;查看源代码也是空的 用dirsearch扫一下 可能是git源码泄露&#xff0c;可以用githack获取源码 python Githack.py http://5063c85b-a33d-4b6f-ae67-262231a4582e.node4.buuoj.cn:81/.git/去工具所在的目录找到index.php文件 打开…

香港服务器减少延迟的几种方法

我们在租用香港服务器时&#xff0c;总觉得网站程序反应太慢。选择了香港服务器的开发商和企业对香港服务器目前的访问速度不满意 怎么办&#xff1f;第一点是换服务器。更换配置更大、带宽更高的服务器&#xff0c;可以更好的解决网站访问速度。如何减少香港服务器的延时 速度…

hm商城微服务远程调用及拆分

RequiredArgsConstructor是Lombok库中的一个注解 它会自动在类中生成一个构造函数&#xff0c;这个构造函数会接收类中所有被标记为final的字段&#xff0c;并将其作为参数。这个注解可以帮助我们减少样板代码&#xff0c;例如手动编写构造函数。 eg&#xff1a; public fin…

【JavaEE】Spring的创建和使用(保姆级手把手图解)

一、创建一个Spring项目 1.1 创建一个Maven项目 1.2 添加 Spring 框架支持 在pom.xml中添加 <dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.3.RELEASE&…

数据仓库架构之详解Kappa和Lambda

目录 一、前言 二、架构详解 1 Lambda 架构 1.1 Lambda 架构组成 1.2 Lambda 特点 1.3 Lambda 架构的优点 1.4 Lambda 架构的不足 2 Kappa 架构 2.1 Kappa 架构的核心组件 2.2 Kappa 架构优点 2.3 Kappa 架构的注意事项 三、区别对比 四、选择时考虑因素 一、前言 …

vue解除数据双向绑定

let obj JSON.parse(JSON.stringify(data));例如&#xff0c;table列表中&#xff0c;点击编辑时&#xff0c;可对val进行如上操作来解除双向绑定

【冒泡排序设计】

【冒泡排序设计】 思路代码结果 思路 冒泡排序这个算法&#xff0c;对于我这样的初学者来说&#xff0c;也不是很简单&#xff01;&#xff01;&#xff01;&#xff08;没有想象的那么简单&#xff09;&#xff01;  它的核心思想是&#xff1a;两两相邻的元素进行比较&#…

Django DRF限流组件

在DRF中&#xff0c;限流发生在认证、权限之后&#xff0c;限流组件的使用步骤&#xff1a; 1、编写自定义限流类&#xff1b; 2、在settings.py中配置redis&#xff1b; 3、安装django-redis; 4、启动redis服务&#xff1b; 5、局部应用&#xff0c;一般是在核心的视图中使用&…

2023前端大厂高频面试题之JavaScript篇(5)

系列文章: 2023前端大厂高频面试题之JavaScript篇(1) 2023前端大厂高频面试题之JavaScript篇(2) 2023前端大厂高频面试题之JavaScript篇(3) 2023前端大厂高频面试题之JavaScript篇(4) 2023前端大厂高频面试题之JavaScript篇(5) 2023前端大厂高频面试题之HTML篇 2023前端大厂高…