QUdpSocket在高频环境下的局限
The limits of QUdpSocket in high frequency enviroments
我的任务是处理读取速率约为 10kHz 的 UDP 数据。我正在使用 Qt 5.13.1 (MinGW32),所以我尝试使用 QUdpSocket
.
我做了一个简单的测试程序,但结果有点令人沮丧。 readyRead()
信号太慢了。出于某种原因,我每 2-4 个信号就有超过 1 或 2 毫秒的延迟。
我制作了一个简单的数据包计数器并将其与我在 wireshark 中看到的进行比较。果然有丢包。
我可以做些什么来提高性能?或者这可能只是 Qt 事件循环的限制?
我 运行 使用 Qt Creator 4.10.0。在 Windows 7.
更新: 根据您的建议:我试过了:
在不同线程中移动套接字。这提供了更多的性能
.. 有点
LowDelayOption = 1 - 我没有注意到任何变化
ReceiveBufferSizeSocketOption - 我没有注意到任何变化
- 阅读时没有使用 QDebug - 我没有检查,只是用于
收集统计数据
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;
}
}
}
在测量代码的时间关键部分时,我建议避免使用 qDebug(或任何其他慢速 print/debug 功能)。它可能对您的实际测量影响太大。
我建议您将从 QElapsedTimer 接收到的计时值存储到一个单独的容器(例如 QVector,或者只是一个随时间平均的 qint64)并且只偶尔显示一次调试消息(每第二个或仅在最后)。这样,由测量引起的开销影响较小。较长时间段内的平均值也有助于测量结果的方差。
我还建议您使用 QElapsedTimer::nsecsElapsed 以避免在高频情况下出现舍入问题,因为 QElapsedTiemr::elapsed 将始终舍入到最接近的毫秒(并且您已经在 1 毫秒范围内测量事物)。
您始终可以在实际显示结果时将纳秒转换为毫秒。
您以 10kHz 的速率接收的数据大小是多少?
您无法在 Windows 上以如此高的速率接收套接字数据包。这是操作系统的限制。即使使用 QAbstractSocket::LowDelayOption 并且如果将您的接收代码移动到这样的无限循环中:
socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
...
for (;;)
{
if(socket->waitForReadyRead(1)) // waits for some events anyway
{
// read here
}
}
或者,您可以将一些时间码字段嵌入到您的数据包结构中,并将多个数据包一起发送,或者使用一些不会丢失数据包的连接。例如,使用 TCP connection + transactions 因为套接字可能出现以下情况:
- 已收到完整数据包
- 只收到部分数据包
- 一起收到了几个数据包
还有,不要尝试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.
我的任务是处理读取速率约为 10kHz 的 UDP 数据。我正在使用 Qt 5.13.1 (MinGW32),所以我尝试使用 QUdpSocket
.
我做了一个简单的测试程序,但结果有点令人沮丧。 readyRead()
信号太慢了。出于某种原因,我每 2-4 个信号就有超过 1 或 2 毫秒的延迟。
我制作了一个简单的数据包计数器并将其与我在 wireshark 中看到的进行比较。果然有丢包。
我可以做些什么来提高性能?或者这可能只是 Qt 事件循环的限制?
我 运行 使用 Qt Creator 4.10.0。在 Windows 7.
更新: 根据您的建议:我试过了:
在不同线程中移动套接字。这提供了更多的性能 .. 有点
LowDelayOption = 1 - 我没有注意到任何变化
ReceiveBufferSizeSocketOption - 我没有注意到任何变化
- 阅读时没有使用 QDebug - 我没有检查,只是用于 收集统计数据
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;
}
}
}
在测量代码的时间关键部分时,我建议避免使用 qDebug(或任何其他慢速 print/debug 功能)。它可能对您的实际测量影响太大。
我建议您将从 QElapsedTimer 接收到的计时值存储到一个单独的容器(例如 QVector,或者只是一个随时间平均的 qint64)并且只偶尔显示一次调试消息(每第二个或仅在最后)。这样,由测量引起的开销影响较小。较长时间段内的平均值也有助于测量结果的方差。
我还建议您使用 QElapsedTimer::nsecsElapsed 以避免在高频情况下出现舍入问题,因为 QElapsedTiemr::elapsed 将始终舍入到最接近的毫秒(并且您已经在 1 毫秒范围内测量事物)。
您始终可以在实际显示结果时将纳秒转换为毫秒。
您以 10kHz 的速率接收的数据大小是多少?
您无法在 Windows 上以如此高的速率接收套接字数据包。这是操作系统的限制。即使使用 QAbstractSocket::LowDelayOption 并且如果将您的接收代码移动到这样的无限循环中:
socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
...
for (;;)
{
if(socket->waitForReadyRead(1)) // waits for some events anyway
{
// read here
}
}
或者,您可以将一些时间码字段嵌入到您的数据包结构中,并将多个数据包一起发送,或者使用一些不会丢失数据包的连接。例如,使用 TCP connection + transactions 因为套接字可能出现以下情况:
- 已收到完整数据包
- 只收到部分数据包
- 一起收到了几个数据包
还有,不要尝试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.