c++qtevent-handlingudpqudpsocket

The limits of QUdpSocket in high frequency enviroments


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

    }
}

Solution

  • 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:

    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.