QTcpSocket ready读取信号在传输数据时停止发出



我在使用 QT 框架为自己编写一些网络应用程序时偶然发现了一个问题。我真的很喜欢信号/老虎机系统,但我感觉我在这里遇到了竞争状态。

我的服务器正在快速发送小的"电报",这些电报卡在一起,因为它们不值得自己的TCP数据包(这很好)。

在客户端,我正在使用套接字 readyRead-slot 并处理我的处理程序插槽"socketHasData"中的传入数据。

问题是,在执行插槽内的代码时,信号似乎没有重新发出。

我在它周围放了一个循环,这有点帮助(我收到更多的电报)。似乎如果退出循环而不是插槽,然后接收数据,则信号被跳过。从插槽返回执行后,我的客户端卡住并永远等待,而数据在套接字缓冲区中堆积。

我能做什么?是否可以在QT中执行此事件?我理解错了吗?如何以这种方式实现基于电报的数据传输?

这里的问题似乎解决了: 如何确保来自 QTcpSocket 的 readyRead() 信号不会被错过? 但我不会犯上述错误:

  • 我不是在叫等待准备阅读
  • 我没有进入任何事件循环
  • 除了主线程之外,我不使用任何线程

并且仍然遇到问题。

下面是一些具有相关行为的示例代码:

服务器

#include <QCoreApplication>
#include <QTcpServer>
#include <QTcpSocket>
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QTcpServer *tcpServer = new QTcpServer(NULL);
tcpServer->listen(QHostAddress("127.0.0.1"), 5573);
tcpServer->waitForNewConnection(-1);
QTcpSocket *socket = tcpServer->nextPendingConnection();
QByteArray telegram;
for (int i = 0; i < 10000; ++i) {
telegram.clear();
QString message = "Test number " + QString::number(i);
// Each "telegram" is just that string and an incrementing number.
telegram.append(static_cast<char>(message.length()));
telegram.append(message.toLocal8Bit());
socket->write(telegram);
}
socket->flush();
socket->close();
tcpServer->close();
return a.exec();
}

客户

#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow() {
delete ui;
}
void MainWindow::on_pushButton_clicked() {
this->socket = new QTcpSocket(this);
this->socket->connectToHost("127.0.0.1", 5573);
connect(this->socket, SIGNAL(readyRead()), this, SLOT(socketHasData()));
}
void MainWindow::socketHasData() {
do {
QByteArray header = this->socket->read(1);
int length = header.at(0);
while (this->socket->bytesAvailable() < length);
QByteArray payload = this->socket->read(length);
qDebug() << "[RX]" << QString::fromLocal8Bit(payload);
} while (this->socket->atEnd() == false); // I added the loop, which helps somewhat
}

客户端标头

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTcpSocket>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_pushButton_clicked();
void socketHasData();
private:
Ui::MainWindow *ui;
QTcpSocket *socket;
};
#endif // MAINWINDOW_H

以下是如何使用Qt通过TCP实现基于电报的数据传输。

寄件人

将数据作为消息发送,标头指定消息大小。

QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream << (quint32)0;
stream << theData;
stream.device()->seek(0);
stream << (quint32)data.size();
m_tcpSocket->write(data);

接收器

在接收方一侧,将readyRead信号连接到读取消息的方法。在这种阅读消息的方法中,请确保您在实际阅读之前拥有整个消息。如果您还没有整条消息,请等待下一个 TCP 有效负载到达,然后再次检查您是否拥有整条消息,直到您拥有它。然后阅读。

...
connect(m_tcpSocket, SIGNAL(readyRead()), this, SLOT(readMessage()));
...
void readMessage() {
// wait for the msgSize
while (m_tcpSocket->bytesAvailable() < qint64(sizeof(quint32))) {
if (!m_tcpSocket->waitForReadyRead()) {
return;
}
}
// get the message size
QDataStream stream(m_tcpSocket);
quint32 msgSize = 0;
stream >> msgSize;
//wait for the whole message
int length = qint64(msgSize - sizeof(quint32));
while (m_tcpSocket->bytesAvailable() < length) {
if (!m_tcpSocket->waitForReadyRead()) {
return;
}
}
// get the message
QByteArray buffer;
buffer.resize(length);
stream.readRawData(buffer.data(), length);
// do something with the message
emit messageRead(buffer);
}

如果你的数据被你"预先打包"成你可以很容易地识别的块,为什么不简单地使用QTimerQTimer::timeout()连接到某个slot_processData()插槽呢? 可以找到合理的超时(使用套接字通常 10-50 毫秒是安全的)。

分析slot_processData槽中的可用数据,将部分数据块保留在"全局"字节数组中以用于传入数据。

这对于"已知"数据实际上非常有效。 我将其用于QSerialPortQTcpSocket两者。 确定QSerialPortQTimer超时值可能有点繁琐。 我发现用户对该值的配置控制通常是必要的,以考虑旧计算机或过载的计算机等。

或者,如果数据的"块"大小相对较小,则每次使用readyRead槽评估所有数据,并将完整的块发送到处理槽/处理程序。 我在串行端口实现中经常使用它,因为硬件设备通常具有简短的指示或回复值。 它消除了QTimer问题。

最新更新