当前位置:   article > 正文

QTcpSocket中readyRead信号不实时触发问题解决_在线程中创建qtcpsocket变量,readyread信号不响应,socket发送时才响应

在线程中创建qtcpsocket变量,readyread信号不响应,socket发送时才响应

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.关键代码

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

先看头文件:

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

再看构造函数:

  1. TcpServer::TcpServer(QObject* parent) : QThread(parent)
  2. {
  3. /*此处省略其它构造内容*/
  4. WORD sockVersion = MAKEWORD(2, 2);
  5. WSADATA wsaData;
  6. if (WSAStartup(sockVersion, &wsaData) != 0)
  7. {
  8. qDebug() << "WSAStartup error"; //WSAStartup返回0表示设置初始化成功
  9. }
  10. m_listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //AF_INET表示IPv4,SOCK_STREAM数据传输方式,IPPROTO_TCP传输协议;
  11. if (m_listenSocket == INVALID_SOCKET)
  12. {
  13. WSACleanup();
  14. }
  15. }

监听函数:

  1. bool TcpServer::StartListen(const QHostAddress& address, quint16 port)
  2. {
  3. if (8899 == port)
  4. {
  5. //绑定IP和端口
  6. //配置监听地址和端口
  7. sockaddr_in addrListen;
  8. addrListen.sin_family = AF_INET; //指定IP格式
  9. addrListen.sin_port = htons(8899); //绑定端口号
  10. addrListen.sin_addr.S_un.S_addr = INADDR_ANY; //表示任何IP
  11. if (bind(m_listenSocket, (SOCKADDR*)&addrListen, sizeof(addrListen)) == SOCKET_ERROR)
  12. {
  13. qDebug() << "绑定失败";
  14. closesocket(m_listenSocket);
  15. }
  16. //开始监听
  17. if (listen(m_listenSocket, 5) == SOCKET_ERROR)
  18. {
  19. qDebug() << "监听出错";
  20. closesocket(m_listenSocket);
  21. }
  22. return true;
  23. }
  24. else
  25. {
  26. return false;
  27. }
  28. }

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

  1. void TcpServer::run()
  2. {
  3. while (true)
  4. {
  5. sockaddr_in remoteAddr; //接收连接到的地址信息
  6. int remoteAddrLen = sizeof(remoteAddr);
  7. m_revSocket = accept(m_listenSocket, (SOCKADDR*)&remoteAddr, &remoteAddrLen); //等待客户端接入,直到有客户端连接上来为止
  8. if (m_revSocket == INVALID_SOCKET)
  9. {
  10. qDebug() << "客户端发出请求,服务器接收请求失败:" << WSAGetLastError();
  11. closesocket(m_listenSocket);
  12. WSACleanup();
  13. }
  14. else
  15. {
  16. qDebug() << "客服端与服务器建立连接成功:" << inet_ntoa(remoteAddr.sin_addr);
  17. goto out; //去往标识符
  18. }
  19. }
  20. out:
  21. while (true)
  22. {
  23. if (m_revSocket != INVALID_SOCKET)
  24. {
  25. char revData[1024] = "";
  26. int res = recv(m_revSocket, revData, 1024, 0);
  27. if (res > 0)
  28. {
  29. qDebug() << "Bytes received:" << res;
  30. ProcData(revData, res); //数据处理函数,包括解包和业务流程等操作
  31. }
  32. }
  33. }
  34. }

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号