The limits of QUdpSocket in high frequency enviroments

1.5k Views Asked by At

I have a task of processing UDP data with ~10kHz read rate. I'm working with Qt 5.13.1 (MinGW32), so I tried to use a QUdpSocket.
I made a simple test program, but the results are a bit frustrating. The readyRead() signal is just too slow. For some reason I'm getting delays over 1 or 2 ms every 2-4 signals.
I made a simple packet counter and compare it with what I see in wireshark. Sure enough there is a packet loss.

What can I do to enhance perfomance? Or maybe it's just the limit of the Qt event loop?

I run it with Qt Creator 4.10.0. On Windows 7.

Update: With your advices: I tried:

  1. Moving socket in different thread. This gives a bit more perfomance .. a very bit

  2. LowDelayOption = 1 - I did not notice any changes

  3. ReceiveBufferSizeSocketOption - I did not notice any changes

  4. No usage of QDebug while reading - I did not check it, just use for collecting statistics

udpproc.h

#ifndef UDPPROC_H
#define UDPPROC_H

#include "QObject"
#include "QUdpSocket"
#include "QHostAddress"
#include "QThread"

#include "QDebug"
#include "networker.h"

class UDPProc : public QObject
{
    Q_OBJECT
public:
    UDPProc();
    ~UDPProc();
private:
    QUdpSocket dataServerSocket;
    NetWorker* netWorker;
    QThread netThread;


};

#endif // UDPPROC_H

udpproc.cpp

UDPProc::UDPProc() {

netWorker = new NetWorker(&dataServerSocket);
netWorker->moveToThread(&netThread);
netWorker->getInnerLoop()->moveToThread(&netThread);

connect(&netThread, SIGNAL(started()), netWorker, SLOT(serverSocketProccessing()));
connect(&this->dataServerSocket, SIGNAL(readyRead()), netWorker->getInnerLoop(), SLOT(quit()));

QString address = "127.0.0.3:16402";
QHostAddress ip(address.split(":").at(0));
quint16 port = address.split(":").at(1).toUShort();
dataServerSocket.bind(ip, port);

//dataServerSocket.setSocketOption(QAbstractSocket::LowDelayOption, 1);
dataServerSocket.moveToThread(&netThread);

netThread.start();

//dataServerSocket.setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 128000);
//qDebug()<<dataServerSocket.socketOption(QAbstractSocket::ReceiveBufferSizeSocketOption).toInt();

}

networker.h

#ifndef NETWORKER_H
#define NETWORKER_H

#include <QObject>
#include "QElapsedTimer"
#include "QEventLoop"
#include "QUdpSocket"
#include "QVector"

class NetWorker : public QObject
{
    Q_OBJECT
private:

    QElapsedTimer timer;
    QVector<long long> times;

    QEventLoop loop;
    QUdpSocket *dataServerSocket;

    char buffer[16286];
    int cnt = 0;
public:
    NetWorker(QUdpSocket *dataServerSocket);
    ~NetWorker();
    QEventLoop * getInnerLoop();

public slots:
    void serverSocketProccessing();

};

#endif // NETWORKER_H

networker.cpp

#include "networker.h"

NetWorker::NetWorker(QUdpSocket *dataServerSocket)
{
    this->dataServerSocket = dataServerSocket;
}

NetWorker::~NetWorker()
{
    delete dataServerSocket;
}

QEventLoop *NetWorker::getInnerLoop()
{
    return &loop;
}

void NetWorker::serverSocketProccessing()
{
    while(true){
        timer.start();
        loop.exec();
        times<<timer.nsecsElapsed();
        while(dataServerSocket->hasPendingDatagrams()){
            dataServerSocket->readDatagram(buffer, dataServerSocket->pendingDatagramSize());
        }
        if (times.size() >= 10000){
            long long sum = 0;
            for (int x : times){
                //qDebug()<<x;
                sum += x;
            }
            qDebug() << "mean: "<<sum/times.size();
            break;
        }

    }
}
2

There are 2 best solutions below

2
On BEST ANSWER

You cannot receive socket packets on Windows with so high rate ever. It is limit of the operating system. Even using QAbstractSocket::LowDelayOption and if move your receiving code into an infinite loop such this:

socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
...

for (;;)
{
    if(socket->waitForReadyRead(1)) // waits for some events anyway
    {
        // read here
    }
}

Alternatively, you can embed some time code field into you data packet structure and send several packets together instead or use some connection where are no packet lost. As example, use TCP connection + transactions because the next situations are possible for a socket:

  • Full packet received
  • Received only part of the packet
  • Received several packets together

Also, do not try to change readBufferSize:

If the buffer size is limited to a certain size, QAbstractSocket won't buffer more than this size of data. Exceptionally, a buffer size of 0 means that the read buffer is unlimited and all incoming data is buffered. This is the default.

This option is useful if you only read the data at certain points in time (e.g., in a real-time streaming application) or if you want to protect your socket against receiving too much data, which may eventually cause your application to run out of memory.

Only QTcpSocket uses QAbstractSocket's internal buffer; QUdpSocket does not use any buffering at all, but rather relies on the implicit buffering provided by the operating system. Because of this, calling this function on QUdpSocket has no effect.

1
On

When measuring timecritical sections of your code I recommend to avoid using qDebug (or any other slow-ish print/debug functionality). It might have too big of an effect on your actual measurements.

What I suggest is that you store the timing values received from QElapsedTimer to a separate container (QVector for example, or just a single qint64 that you average over time) and only show the debug messages once in a while (every second or only at the end). That way the overhead caused by the measurement is has less effect. The averaging over a longer period of time will also help with the variance of your measurement results.

I also recommend you to use QElapsedTimer::nsecsElapsed to avoid rounding issues in high frequency situations because QElapsedTiemr::elapsed will always round to the closest millisecond (and you are already measuring thing within 1ms region).

You can always convert the nanoseconds to milliseconds later on when actually showing the results.

What is the size of the data you are receiving at 10kHz rate?