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

这篇具有很好参考价值的文章主要介绍了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()<<"收到心跳响应包";
    }
}

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

        文章来源地址https://www.toymoban.com/news/detail-758291.html

到了这里,关于C++ Qt TCP协议,处理粘包、拆包问题,加上数据头来处理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 「 计算机网络 」TCP的粘包拆包问题

    参考鸣谢 大病初愈,一分钟看懂TCP粘包拆包 雷小帅 TCP 的粘包拆包以及解决方案 一乐说 当我们在进行网络传输时,由于各种原因,数据包的发送和接收可能会出现粘包和拆包的问题。粘包和拆包都是数据分组错误的情况,其中粘包指的是多个数据包被合并成一个,而拆包则

    2024年02月01日
    浏览(35)
  • 【计网】一起聊聊TCP的粘包拆包问题吧

    在TCP中,粘包和拆包问题是十分常见的,如 基于TCP协议 的RPC框架、Netty等。 粘包(Packet Stickiness) 指的是在网络通信中,发送方连续发送的多个小数据包被接收方一次性接收的现象。这可能是因为底层传输层协议(如TCP)会将 多个小数据包合并成一个大的数据块 进行传输,导

    2024年04月12日
    浏览(33)
  • 粘包/拆包问题一直都存在,只是到TCP就拆不动了。

    OSI open-system-Interconnection TCP/IP 5层协议栈 应用层和操作系统的边界是 系统调用 ,对应到网络编程是socket api TCP/UDP 概况 TCP粘包问题 TCP/IP报头深思 定义了网络框架,以层为单位实现协议,同时控制权逐层传递。 OSI实际并没有落地,TCP/IP 5层协议栈是目前主流的落地实现 。 TC

    2024年02月03日
    浏览(68)
  • Netty自定义应用层协议逃不开的粘包和拆包处理

    导致一次发送的数据被分成多个数据包进行传输,或者多次发送的数据被粘成一个数据包进行传输 使用TCP进行数据传输时,TCP是一种有序的字节流,其中是一个一个的数据报文发送到系统的缓冲区中。因此在发送端和接收端之间无法保证数据的分割和边界。这就可能导致数据

    2023年04月23日
    浏览(97)
  • 说说 TCP的粘包、拆包

    拆包和粘包是在socket编程中经常出现的情况, 在socket通讯过程中,如果通讯的一端一次性连续发送多条数据包,tcp协议会将 多个数据包打包 成一个tcp报文发送出去,这就是所谓的 粘包 。 如果通讯的一端发送的数据包超过一次tcp报文所能传输的最大值时,就会将 一个数据包

    2024年02月09日
    浏览(44)
  • Unity-TCP-网络聊天功能(一): API、客户端服务器、数据格式、粘包拆包

    TCP是面向连接的。因此需要创建监听器,监听客户端的连接。当连接成功后,会返回一个TcpClient对象。通过TcpClient可以接收和发送数据。 VS创建C# .net控制台应用 项目中创建文件夹Net,Net 下添加TCPServer.cs类,用来创建TCPListener和Accept客户端连接,实例化一个TCPServcer放在Main函数

    2024年02月07日
    浏览(65)
  • Socket TCP/IP协议数据传输过程中的粘包和分包问题

    一:通过图解法来描述一下分包和粘包,这样客户更清晰直观的了解: 下面对上面的图进行解释: 1.正常情况:如果Socket Client 发送的数据包,在Socket Server端也是一个一个完整接收的,那个就不会出现粘包和分包情况,数据正常读取。 2.粘包情况:Socket Client发送的数据包,

    2024年02月12日
    浏览(42)
  • TCP的粘包、拆包、解决方案以及Go语言实现

    TCP的粘包和拆包问题往往出现在基于TCP协议的通讯中,比如RPC框架 在使用TCP进行数据传输时,由于TCP是基于字节流的协议,而不是基于消息的协议,可能会出现粘包(多个消息粘在一起)和拆包(一个消息被拆分成多个部分)的问题。这些问题可能会导致数据解析错误或数据

    2024年02月15日
    浏览(80)
  • workerman 自定义的协议如何解决粘包拆包

    前言:         由于最近在使用 workerman 实现 Unity3D 联机游戏的服务端,虽然也可以通过 TCP 协议直接通信,但是在实际测试的过程中发现了一些小问题。         比如双方的数据包都是字符串的方式吗,还有就因为是字符串就需要切割,而有时候在客户端或服务端接收时都

    2024年02月09日
    浏览(38)
  • TCP协议-TCP粘包问题

            我们知道,TCP是一个面向字节流的传输层协议。“流” 意味着 TCP 所传输的数据是没有边界的。这不同于 UDP 协议提供的是面向消息的传输服务,其传输的数据是有边界的。TCP 的发送方无法保证对方每次收到的都是一个完整的数据包。于是就有了粘包、拆包问题的

    2023年04月08日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包