QTcpSocket中readyRead信号不实时触发问题解决

这篇具有很好参考价值的文章主要介绍了QTcpSocket中readyRead信号不实时触发问题解决。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1.问题描述

在Qt中使用Tcp通讯时的惯常做法是在服务端将QtcpSocketreadyRead信号与处理业务的槽函数关联,这样每当有新的通讯数据时触发readyRead信号,进而通过槽函数处理业务流程。然而,readyRead信号与客户端的write函数并没有必然的一一对应关系。因此,对一些特别依赖数据实时性的应用场景就会出现通讯中断的情况。

关联readyRead信号的一般形式如下:

QObject::connect(mpSocket,&QTcpSocket::readyRead,this,&TcpServer::slotReadData);

例如,在一次业务流程中,当服务端根据客户端的回复指令下发指令时,由于readyRead信号没有实时触发,导致服务端没有及时收到回令而业务中断(用Wireshark抓包显示数据的确发送成功了)。当下一次重新开始时,才把上一次滞留的回令连带新的回令以粘包的形式发给服务端。此时,本意是完全从头再来,却收到上一次的滞留回令,此时业务逻辑就很有可能出错。在这种情形中,Qt的信号与槽机制参与TCP通讯就存在局限性。我们迫切需要换一种思路去实时获取TCP传输过来的数据。

2.原因分析

首先,必须明确发送端write一次,接收方就会有新数据到达,readyRead()信号就会触发一次,这种理解不对!

发送和接收没有必然一一对应关系。发送端write()函数调用一次,若这一次write了较大数据(2M),那么接收方readyRead()信号往往会触发两次以上,反过来,如果发送方write()函数被调用了两次或是以上,接收方的readyRead信号也可能只调用一次。

所谓的有新数据来,readyRead信号就会触发一次,实际上不是指从发送端有新数据来到接收端计算机,而是数据从接收计算机的Tcp/ip协议栈到达Qt应用程序,即系统io缓冲区到达Qt应用程序,数据从系统到达Qt应用程序readyRead信号就会触发一次。

综上,究其根本是QT的TCP通讯并没有丢数据,只是数据滞留在了io缓冲区!

3.解决方法

本人尝试了起一个线程不断轮询调用QtcpSocket的readAll()或者read()函数,只是偶尔能够全部及时取到TCP通讯数据,很多时候也拿不到io缓冲区的滞留数据。甚至,起两个线程,一个用于收发数据(对应readAll()write()函数),另外一个用队列里的指令驱动业务流程,这样也不行,连最基本的通讯连接都出了问题。

既然QT的接口有局限性,就尝试调用windows自带的socket接口。最终,根据这个思路,调用WinSock的的recv()接口,完美解决问题!

4.关键代码

这里省略了业务流程以及其它不相干的代码,只突出解决问题的关键代码。

先看头文件:

#include <WinSock.h>
class TcpServer : public QThread
{
	Q_OBJECT
public:
	explicit TcpServer(QObject* parent = 0);
	~TcpServer();
	void run();
	bool StartListen(const QHostAddress& address = QHostAddress::Any, quint16 port = 0);
private:
	void ProcData(char revData[], int len);
private:
	SOCKET m_listenSocket;
	SOCKET m_revSocket;  //对应所建立连接的套接字的句柄
};

再看构造函数:

TcpServer::TcpServer(QObject* parent) : QThread(parent)
{
	/*此处省略其它构造内容*/

	WORD sockVersion = MAKEWORD(2, 2);
	WSADATA wsaData;
	if (WSAStartup(sockVersion, &wsaData) != 0)
	{
		qDebug() << "WSAStartup error"; //WSAStartup返回0表示设置初始化成功
	}
	m_listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //AF_INET表示IPv4,SOCK_STREAM数据传输方式,IPPROTO_TCP传输协议;
	if (m_listenSocket == INVALID_SOCKET)
	{
		WSACleanup();
	}
}

监听函数:

bool TcpServer::StartListen(const QHostAddress& address, quint16 port)
{
	if (8899 == port)
	{
		//绑定IP和端口
		//配置监听地址和端口
		sockaddr_in addrListen;
		addrListen.sin_family = AF_INET;     //指定IP格式
		addrListen.sin_port = htons(8899);   //绑定端口号
		addrListen.sin_addr.S_un.S_addr = INADDR_ANY;  //表示任何IP
		if (bind(m_listenSocket, (SOCKADDR*)&addrListen, sizeof(addrListen)) == SOCKET_ERROR)
		{
			qDebug() << "绑定失败";
			closesocket(m_listenSocket);
		}
		//开始监听
		if (listen(m_listenSocket, 5) == SOCKET_ERROR)
		{
			qDebug() << "监听出错";
			closesocket(m_listenSocket);
		}
		return true;
	}
	else
	{
        return false;
	}
}

关键的地方来了,线程的run()函数。这里之所以要用两个while循环是因为在软件启动后,流程走到accept函数后就一直处于等待状态,如果把第一个while循环里的内容写到构造函数里,你会发现软件卡住了!所以,这里的操作是先进入第一个循环等待接入客户端,一旦成功,马上跳入第二个循环。因此,第二个循环才是线程真正接收数据,处理业务的地方。文章来源地址https://www.toymoban.com/news/detail-802297.html

void TcpServer::run()
{
	while (true)
	{
		sockaddr_in remoteAddr;   //接收连接到的地址信息
		int remoteAddrLen = sizeof(remoteAddr);
		m_revSocket = accept(m_listenSocket, (SOCKADDR*)&remoteAddr, &remoteAddrLen);  //等待客户端接入,直到有客户端连接上来为止
		if (m_revSocket == INVALID_SOCKET)
		{
			qDebug() << "客户端发出请求,服务器接收请求失败:" << WSAGetLastError();
			closesocket(m_listenSocket);
			WSACleanup();
		}
		else
		{
			qDebug() << "客服端与服务器建立连接成功:" << inet_ntoa(remoteAddr.sin_addr);
			goto out; //去往标识符
		}
	}
out:
	while (true)
	{
		if (m_revSocket != INVALID_SOCKET)
		{
			char revData[1024] = "";
			int res = recv(m_revSocket, revData, 1024, 0);
			if (res > 0)
			{
				qDebug() << "Bytes received:" << res;
				ProcData(revData, res); //数据处理函数,包括解包和业务流程等操作
			}
		}
	}
}

到了这里,关于QTcpSocket中readyRead信号不实时触发问题解决的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 多媒体库SDL以及实时音视频库WebRTC中的多线程问题实战详解

    目录 1、概述 2、开源跨平台多媒体库SDL介绍 3、开源音视频实时通信库WebRTC介绍

    2024年02月08日
    浏览(54)
  • 解决Iframe交互事件window.addEventListener触发多次问题

    addEventListener() 方法用于向指定元素添加事件句柄。 提示: 使用 removeEventListener() 方法来移除 addEventListener() 方法添加的事件句柄。 当我们审批流交互用到window.postMessage结合window.addEventListene这种消息传递技术来实现安全的通信。简单的来讲就是进行事件交互,如当我们有以下

    2024年02月11日
    浏览(40)
  • JQuery动态生成的按钮无法触发问题与解决方法

    利用JQuery动态添加的按钮无法通过 $(selector).click 方法触发点击事件 append中的节点是在整个文档加载完之后开始添加的, 因此页面不会为append的元素初始化添加点击事件 使用$(document).on()方法添加点击事件

    2024年01月20日
    浏览(36)
  • elementUI中input回车触发页面刷新问题和解决方法

    今天在做项目的的时候发现创建的el-form表单内单个el-input框输入值后点回车会导致页面刷新的问题,于是好奇心驱使下去查了vue中的form表单内的单input框也会跳转页面也就是页面刷新的问题。查了下 :::tip W3C 标准中有如下规定: When there is only one single-line text input field in a fo

    2024年02月08日
    浏览(36)
  • 解决鼠标无法划到hover触发后的显示区域的问题

    现象如下:  当鼠标划入上方链接通过hover触发的显示区域时,该区域就会消失。也就是鼠标无法进入该区域。 造成这个问题的根本原因是: hover效果的交互区域太小,导致鼠标很容易就脱离了hover的触发边界 解决办法: 通过设置line-height撑开被hover元素的容器高度,扩大h

    2024年02月09日
    浏览(43)
  • 解决el-form一进页面就会触发表单校验问题

    预期效果是:打开页面,外出地点和其他属性一样,不会自动触发表单非空校验,而是在操作当前属性时触发。 解决思路: 设置初始值为空数组即可 若不是多选,是输入框或者其他就初始化对应的值即可。思路大概是这样~

    2024年02月16日
    浏览(51)
  • FPGA外部触发信号毛刺产生及滤波

    1、背景         最近在某个项目中,遇到输入给FPGA管脚的外部触发信号因为有毛刺产生,导致FPGA接收到的外部触发信号数量多于实际值。比如:用某个信号源产生1000个外部触发信号(上升沿触发方式)给到FPGA输入IO,实际上FPGA内部逻辑判定接收到的触发信号数量大于100

    2024年02月13日
    浏览(82)
  • Element UI 中el-input 框回车触发页面刷新问题及解决

    问题描述 当el-input 获取焦点后按到回车按钮会刷新当前页面 问题解决 在el-form标签增加表单事件@submit.native.prevent,防止搜索框回车键刷新整个页面

    2024年02月09日
    浏览(55)
  • 【uniapp 小程序】解决 map 组件出现标点(地图)自动偏移或 @regionchange 频繁触发的问题

    在业务开发中出现了地图的中心标点向右侧缓慢移动的问题,在我解决后又发现了 @regionchange 方法出现了无限调用的问题。这几个问题属于微信 map 地图组件迟迟未修复的bug。 本文仅解决与我相似的问题以做参考,并会附上对应的问题与参考的博客。 1、地图无操作下出现缓

    2024年02月03日
    浏览(40)
  • Element plus中el-input 框回车触发页面刷新问题及解决方案

    技术:vue3+element plus的UI框架 今天做了一个小小的功能,就是基于elementUi框架的一个输入框,需要监听输入框的回车事件,然后调取接口。 代码如下: 在输入框回车后会刷新页面,查询的原因如下 在 el-form 里加上 @submit.prevent , input输入框里可以使用 @keyup.enter 监听回车事件

    2024年02月15日
    浏览(62)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包