0

I stumbled upon an issue while programming some network application for myself using the QT-Framework. I really like the signals / slot system but I have the feeling I'm running into a race condition here.

My server is rapidly sending small "telegrams" which are stuck together because they are not worth a TCP-packet of their own (thats fine).

On the client side, I am using the sockets readyRead-slot and process the incomming data in my handler-slot "socketHasData".

The problem is, it seems that the signal is not re-emitted while the code inside the slot is executed.

I put a loop around it, which helps a little bit (I receive more telegrams). It seems like if the loop but not the slot is exited and then data is received, the signal gets skipped. After execution returns from the slot, my client is stuck and waiting forever while the data is piling up in the sockets buffer.

What can I do? Is it possible todo this event based in QT at all? Did I understand something wrong? How can I immplement telegram based transfer of data this way?

Here the problem seems solved: How to make sure that readyRead() signals from QTcpSocket can't be missed? but I don't make the mentioned mistakes:

  • I'm not calling waitForReadyRead
  • I'm not entering any event loop
  • I don't use any thread besides the main thread

and still run into the issues.

Here is some example code with the behaviour in question:

Server

#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();
}

Client

#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
}

Client-Header

#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
  • A thread can not do 2 things at once. When it is executing your slot, it can't emit the signal about newly arriving data. The signal will be emitted only when you return control to your event. You need to make your slot return as soon as possible, and restructure your program to follow an event-driven architecture. Your `socketHasData` slot seems to get stuck in one of the loops you have put in there. You can find out which loop it is by using a debugger. – Mike Mar 02 '18 at 16:33
  • 2
    Your `MainWindow::socketHasData` implementation appears to assume that it will always read an integer number of complete telegrams. That's not how `TCP` works. Also, you have `while (this->socket->bytesAvailable() < length);`. If the condition holds true then this is just a busy-waiting loop that will never finish. – G.M. Mar 02 '18 at 16:33

2 Answers2

0

If your data is "pre-packaged" by you into chunks which can easily be recognized by you, why not simply use a QTimer and connect QTimer::timeout() to some slot_processData() slot? A reasonable timeout can be found, (10-50 ms is typically safe using sockets).

Analyze your available data in the slot_processData slot, keeping partial data chunks back in a "global" byte array for incoming data.

This is actually quite efficient for "known" data. I use this for QSerialPort and QTcpSocket both. The QTimer timeout value for QSerialPort can be a bit more tedious to determine. I find that user configuration control over that value is typically necessary to account for old computers or overloaded computers, etc.

ALTERNATELY If the data is relatively small in "chunk" size, use the readyRead slot to evaluate all data each time and send complete chunks to the processing slot/handler. I use this in serial port implementations quite a lot since it is typical for hardware devices to have short indications or reply values. It eliminates the QTimer issues.

guitarpicva
  • 432
  • 3
  • 10
0

Here is how to implement a telegram based transfer of data over TCP using Qt.

Sender

Send your data as messages with a header specifying the message size.

QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);

stream << (quint32)0;
stream << theData;
stream.device()->seek(0);
stream << (quint32)data.size();

m_tcpSocket->write(data);

Receiver

On the receiver's side, connect the readyRead signal to the method for reading the messages. And in this method for reading the messages make sure you have the whole message before actually reading it. If you do not have the whole message yet, wait for the next TCP payload to arrive and check again if you do have the whole message until you have it. Then read.

...
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);

}
Matt
  • 15
  • 7