QTcpSocket readyRead信号在传输数据时停止发射

时间:2018-03-02 16:14:15

标签: c++ qt sockets networking race-condition

我在使用QT-Framework为自己编写一些网络应用程序时偶然发现了一个问题。我非常喜欢信号/插槽系统,但我感觉我在这里遇到了竞争状态。

我的服务器正在快速发送小型“电报”,因为它们不值得拥有自己的TCP数据包(很好)。

在客户端,我使用套接字readyRead-slot并处理我的处理程序槽“socketHasData”中的incomming数据。

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

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

我该怎么办?是否可以根据QT进行此次活动?我明白了什么问题吗?如何以这种方式实现基于电报的数据传输?

这里的问题似乎已经解决了: How to make sure that readyRead() signals from QTcpSocket can't be missed? 但我没有提到上述错误:

  • 我没有打电话给waitForReadyRead
  • 我没有进入任何事件循环
  • 我不使用除主线程之外的任何线程

仍然遇到问题。

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

服务器

#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

2 个答案:

答案 0 :(得分:0)

如果您的数据被“预打包”到您可以轻松识别的块中,为什么不简单地使用QTimer并将QTimer::timeout()连接到某个slot_processData(QByteArray incoming)位置?可以找到合理的超时(使用套接字通常是10-50毫秒)。

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

这对于“已知”数据实际上非常有效。我将它用于QSerialPortQTcpSocketQTimer的{​​{1}}超时值可能更难以确定。我发现通常需要对该值进行用户配置控制以考虑旧计算机或过载的计算机等。

<强>交替 如果数据的“块”大小相对较小,请使用QSerialPort槽每次评估所有数据,并将完整的块发送到处理槽/处理程序。我在串口实现中使用它非常多,因为硬件设备通常具有短指示或回复值。它消除了readyRead问题。

答案 1 :(得分:0)

这里是如何使用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);

}