Qt的信号与槽是很好的去耦合机质,但在实际使用中,要特别注意它的性能问题。信号与槽不适合非常密集的触发,切记越靠近动态语言的东西(典型的是Qt的元对象系统 meta-object)性能肯定越差。

本次记录的测试,即在生产环境遇到的典型案例。含有煤层断面、地下水的回波是勘探中重要的传感器数据。一般由多个传感器汇聚到计算终端(上位机)上,进行模型解算。以前一直在上位机上采用TCP Server, 板卡连接tcp 端口发东东,没有遇到丢包的现象。近期,由于传感器升级,其接口为UDP格式,不得不采用UDP Server收集数据,遇到了丢包问题。

现象

声波卡发来数据包,包头含有计数器,因此,可以通过比较收到的包个数和计数器的数值,确定有木有丢包。发包程序是SoC的Linux,网络连接是4G。

参照以前TcpSocket的套路,使用信号与槽,响应readyRead直接读取。由于UDP包与包是天然分割开的,不会类似TCPSocket那样连接为一个没头没尾的整体,读取起来简单多了。但是,上位机处理程序出来的结果总是不对,检查发现,数据相当不连续,每10000个包,能丢掉1000~3000个!

这显然是不行的,联系厂家,改回Tcp。可是厂家会拒绝修改,理由是他们出场测试好了,没有问题。用厂家的windows控制台测试程序测试,真的一个不丢。

分析

由于厂家给的程序没问题,那问题出在上位机的程序上。难道是QUDPSocket不能使用信号与槽?不对啊,Tcp信号与槽,以前百兆视频都Ok的,因为它有Buffer的——查一查文档:

[virtual] void QAbstractSocket::setReadBufferSize(qint64 size)
Sets the size of QAbstractSocket's internal read buffer to be size bytes.
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.
See also readBufferSize() and read().

看到了最后一句“ 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.”
原来,Qt的QUDPSocket是不用Buffer的。这就不奇怪了,信号与槽的延迟大,肯定会丢包。

测试

为了抛开网络等因素,比较信号与槽、简单阻塞循环、windows本地API三者的UDP接收性能,我们做了一个本地localhost下的极限测试程序。

测试方法:
每20毫秒发一波,一波100包,每包在4KB以内。测试收到10000个包时,计数的差值(发送了多少包),以体现丢包情况。

1 笔记本计算机环境

QUdpSocket LOOP:
Send 10277, Recv 10000, Lost 277.
QUdpSocket Signal and Slots:
Send 12482, Recv 10000, Lost 2482.
Local Socket :
Send 10003, Recv 10000, Lost 3.

2 高性能计算机环境

QUdpSocket LOOP:
Send 10028, Recv 10000, Lost 28.
QUdpSocket Signal and Slots:
Send 10247, Recv 10000, Lost 247.
Local Socket :
Send 10000, Recv 10000, Lost 0.

改进

  1. 避免动态内存分配。不要使用 QByteArray等动态结构,而是老实的用C的指针接口。
  2. 避免额外的参数获取(如源地址、端口)。
    可显著改善性能,结果(高性能计算机):

QUdpSocket LOOP:
Send 10000, Recv 10000, Lost 0.
QUdpSocket Signal and Slots:
Send 10000, Recv 10000, Lost 0.
Local Socket :
Send 10000, Recv 10000, Lost 0.

3 结论(2022补充)

  1. 丢包与计算机CPU的性能关系很大。像便携式笔记本电脑,丢包非常严重。高性能计算机,就好的多。
  2. 使用信号与槽的时效性最差,其次为 QUdpSocket的Loop模式。
  3. 使用本地Socket API,性能最好,在i7上一个不丢。

尽管UDP协议不保证数据完整性,但各个框架应该尽可能地保证高性能。QUdpSocket应该提供缓存,以便大幅度减少丢包。

2022年,测试了Qt6,发现在性能上有所加强。另外,注意Qt readDatagram 使用分配好的内存,会显著提高性能。在我的 i7 十代计算机上,Loop模式已经和本地没有差别了。

4 UDP之外的选择

在2022年,尝试使用以太网协议直接进行吞吐,效果很好。这种情况适用于网线直连的情况。请参考这里:
https://goldenhawking.blog.csdn.net/article/details/126292692

附件-测试程序

test.pro

QT -= gui
QT += networkCONFIG += c++11 console
CONFIG -= app_bundleQMAKE_LIBS += -lws2_32
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \main.cppHEADERS += \qtest.h

qtest.h

#ifndef ALLINONE_H
#define ALLINONE_H
#include <QCoreApplication>
#include <QThread>
#include <QUdpSocket>
#include <QHostAddress>
#include <QTextStream>
#include <QNetworkDatagram>
#include <windows.h>
static const int tests = 10000;
static const int port = 23456;
static const int period = 20;
static const int batch = 100;
static const int packsize = 512;//pack size in 8 bytes
/*!* \brief test_Loop Test QUDPSocket using simple loop.* \return total send packages.*/
inline int test_Loop()
{qint64 startID = -1, endID = -1;QUdpSocket * p = new QUdpSocket;char revdata[packsize*8];const long long * d = reinterpret_cast<const long long *>(revdata);if (p->bind(QHostAddress::LocalHost,port)){for (int i=0;i<tests;++i){while (!(p->hasPendingDatagrams()));p->waitForReadyRead();int sz = p->readDatagram(revdata,packsize*8);if (sz<8)continue;if (d){if (startID==-1)startID = *d;endID = *d;}}p->close();}p->deleteLater();return endID - startID + 1;
}/*!* \brief The TestObj class test QUDPSocket using signal-slots.*/
class TestObj:public QObject
{Q_OBJECT
public:explicit TestObj(QObject * pa = nullptr):QObject(pa){connect (this,&TestObj::_cmd_start,this,&TestObj::slot_start,Qt::QueuedConnection);}bool isFinished() const {return m_finished;}int startID() const {return m_startID;}int endID() const {return m_endID;}
protected slots:void readPdDt(){static char revdata[packsize*8];static const long long * d = reinterpret_cast<const long long *>(revdata);while (m_sock->hasPendingDatagrams()){int sz = m_sock->readDatagram(revdata,packsize*8);if (sz<8)continue;if (++m_total>tests){m_sock->close();m_finished = true;QThread::currentThread()->quit();break;}if (d){if (m_startID==-1)m_startID = *d;m_endID = *d;}}}
public:void start(){emit _cmd_start();}
private slots:void slot_start(){m_sock = new QUdpSocket(this);connect (m_sock,&QUdpSocket::readyRead,this,&TestObj::readPdDt);if (!m_sock->bind(QHostAddress::LocalHost,port)){m_sock->deleteLater();return;}}
signals:void _cmd_start();
private:int m_startID = -1;int m_endID = -1;int m_total = 0;bool m_finished = false;QUdpSocket * m_sock = nullptr;
};/*!* \brief local_test test udp recv using local socket API* \return total send*/
inline int local_test()
{//init wsaconst WORD sockVision = MAKEWORD(2,2);WSADATA wsadata;//socketsSOCKET sock;if( WSAStartup(sockVision,&wsadata) != 0 ){printf("WSA initial failed.\n");return 0;}//Create socksock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);if(sock == INVALID_SOCKET){printf("socket creating failed.\n");return 0;}struct sockaddr_in sin;//Bind to Localhost:portsin.sin_family = AF_INET;sin.sin_port = htons(port);sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//local hostif( bind(sock,(LPSOCKADDR)&sin,sizeof(sin)) == SOCKET_ERROR ){printf("Can not bind to Address.\n");return 0;}struct sockaddr_in remoteAddr;int nAddrlen = sizeof(remoteAddr);int startID = -1, endID = -1;int total = 0;char revdata[packsize*8];const long long * pL = reinterpret_cast<const long long *>(revdata);while (total<tests){int rev  = recvfrom(sock,revdata,packsize*8,0,(SOCKADDR*)&remoteAddr,&nAddrlen);if(rev >=8){if (startID==-1)startID = *pL;endID = *pL;++total;}}closesocket(sock);WSACleanup();return endID - startID + 1;
}/*!* \brief The sendingThread class send testing packages.*/
class sendingThread: public QThread{Q_OBJECT
public:explicit sendingThread(QObject * pa = nullptr):QThread(pa){}void stop(){term = true;}
protected:void run() override{qint64 SendBuf[packsize] = {0};const char * dtp = reinterpret_cast<const char *>(SendBuf);int iResult;WSADATA wsaData;SOCKET SendSocket = INVALID_SOCKET;sockaddr_in RecvAddr;//----------------------// Initialize WinsockiResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (iResult != NO_ERROR) {wprintf(L"WSAStartup failed with error: %d\n", iResult);return;}//---------------------------------------------// Create a socket for sending dataSendSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (SendSocket == INVALID_SOCKET) {wprintf(L"socket failed with error: %ld\n", WSAGetLastError());WSACleanup();return;}//---------------------------------------------RecvAddr.sin_family = AF_INET;RecvAddr.sin_port = htons(port);RecvAddr.sin_addr.s_addr = inet_addr("127.0.0.1");//---------------------------------------------// Send a datagram to the receiverwhile(!term){for (int i=0;i<batch;++i){const int BufLen = sizeof(SendBuf) - rand()%(sizeof(SendBuf)-100);++SendBuf[0];iResult = sendto(SendSocket,dtp, BufLen, 0, (SOCKADDR *) & RecvAddr, sizeof (RecvAddr));if (iResult == SOCKET_ERROR) {wprintf(L"sendto failed with error: %d\n", WSAGetLastError());closesocket(SendSocket);WSACleanup();return ;}}if (period>0)msleep(period);}//---------------------------------------------// When the application is finished sending, close the socket.wprintf(L"Finished sending. Closing socket.\n");iResult = closesocket(SendSocket);if (iResult == SOCKET_ERROR) {wprintf(L"closesocket failed with error: %d\n", WSAGetLastError());WSACleanup();return;}//---------------------------------------------// Clean up and quit.wprintf(L"Exiting.\n");WSACleanup();}
private:bool term = false;
};
#endif // ALLINONE_H

main.cpp

#include "qtest.h"int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);//Start SendingsendingThread * tsend = new sendingThread;tsend->start(QThread::HighestPriority);QThread::msleep(3000);printf("Start...\n");//test UDP loopint total_loop = test_Loop();printf("QUdpSocket LOOP:\n\tSend %d, Recv %d, Lost %d.\n",total_loop,tests,total_loop-tests );QThread::msleep(3000);printf("Start...\n");//test ClassQThread * trecv = new QThread;TestObj * sobj = new TestObj;sobj->moveToThread(trecv);trecv->start();sobj->start();trecv->wait();sobj->deleteLater();trecv->deleteLater();const int totaltest_ss = sobj->endID()-sobj->startID()+1;printf("QUdpSocket Signal and Slots:\n\tSend %d, Recv %d, Lost %d.\n",totaltest_ss,tests,totaltest_ss-tests );QThread::msleep(3000);printf("Start...\n");//Test Localconst int local_total = local_test();printf("Local Socket :\n\tSend %d, Recv %d, Lost %d.\n",local_total,tests,local_total-tests );//endtsend->stop();tsend->wait();tsend->deleteLater();QCoreApplication::processEvents();getchar();return 0;
}

QUdpSocket 丢包测试与解决相关推荐

  1. 数据丢包怎么修复_网络丢包率如何解决

    网络丢包率如何解决 网络丢包是我们在使用 ping (检测某个系统能否正 常运行) 对目站进行询问时, 数据包由于各 种原因在信道中丢失的现象. ping 使用了 ICMP 回送请求与回送回答报文. ...

  2. ATKKPING(网络丢包测试工具)

    下载链接 ATKKPING绿色版不需要安装就可以使用了,这是一款ping的增强程序,网络丢包率测试软件.主要用来进行丢包测试使用,可以测试网络环境!可以报告带宽,延迟抖动和数据包丢失,还可以用来测试一 ...

  3. 某公司R2631E以太口通过SDH接新桥的ATM交换机,出现丢包问题的解决方法

    某公司R2631E以太口通过SDH接新桥的ATM交换机,出现丢包问题的解决方法 问题描述 某电力公司R2631E的以太口通过SDH接新桥的ATM交换机,出现丢包的情况,display int e 1 ...

  4. 以太网 TCP协议交互过程中出现丢包时的解决机制,超时重传、快速重传、SACK与DSACK

    2.7.3 以太网 TCP协议(TCP交互过程中出现丢包时的解决机制-列举部分) 参考:CSDN_TCP的重传机制_博主.Pr Young,对描述进行了整理与结合个人的理解进行编写. 一.超时重传机制 ...

  5. linux6.5系统间歇性丢包,路由器经常丢包、掉线解决方法大全

    路由器老掉线是经常出现的问题,你上网都可能遇到:丢包.上网慢.掉线.上不去网.不能浏览网页.卡.信号差.误码率高.信号延迟.连接失败.不稳定.上不去.死机.无故中断等现象,这到底是怎么回事,如何解决这 ...

  6. 免费的网络带宽、延迟、丢包测试工具 HoloWAN_Recorder_Pro_v2.1.0 安卓端

    免费的网络带宽.延迟.丢包测试工具 HoloWAN Recorder pro可以测量网络的带宽.延迟.丢包以及网络抖动. 测量结果可以通过图表展现网络状况. 并且可以录制一段时间内的网络状况,配合Ho ...

  7. python socket通信 recv 丢包_用clumsy模拟丢包测试socket库的失败重传

    用python的socket库写了通信小程序,现在我需要通过软件模拟出在网络极差的情况下,socket底层解决丢包问题的能力怎么样,我一开始想的是分别在linux和windowns下分别测试,后来一想 ...

  8. 手机测试wifi的延迟的软件,app的延迟和丢包测试(六)

    一,弱网测试的背景 1,用户体验 APP使用过程中,弱网的高延迟和高丢包,在实时性要求非常高,容易伤害用户体验 2,非正常情况下,出现bug概率会增加 在解决日常的支持需求中,经常会遇到一些用户反馈一 ...

  9. TCP丢包原因、解决办法

    TCP是基于不可靠的网络实现可靠的传输,肯定也会存在掉包的情况,如果通信中发现缺少数据或者丢包,那么,最大的可能在于程序发送的过程或者接收的过程出现问题. 例如服务端要给客户端发送大量数据,Send频 ...

  10. linux网络丢包测试工具,用NETEM模拟网络丢包 (转)

    [linux] tc netem 模拟网络丢包linux下的tc可以操纵网络,比如分配带宽给不同的应用.模拟网络时延.模拟糟糕网络环境下的丢包等. 但在实际使用模拟丢包时,我们 发现了问题:两台服务器 ...

最新文章

  1. Awk 实例,第 1 部分
  2. 推荐一篇关于java集合的博文,写的很nice
  3. redis scan 效率太慢_Redis 基础、高级特性与性能调优(下)
  4. 我遇到的Quartus II警告及原因——持续更新
  5. [机器学习]理解熵,交叉熵和交叉熵的应用
  6. [剑指offer]面试题3:二维数组中的查找
  7. 前端学习(1523):vue-cli项目目录介绍
  8. linux php项目启动_Linux上实现Node.js项目自启动
  9. step7设置pcpg_STEP7 PC/PG设置的疑惑
  10. Linux批量部署无密钥脚本
  11. Nginx伪静态配置和常用Rewrite伪静态规则
  12. Windows右键菜单设置与应用技巧(转)
  13. C#学习之IntPtr类型
  14. 图论算法真的那么难吗?知识点都在这了……
  15. 知道华为HMS ML Kit文本识别、银行卡识别、通用卡证识别、身份证识别的区别吗?深度好文教你区分
  16. python爬取同花顺_Python爬虫-同花顺行业历史数据及成分股
  17. 欢聚时代java面试面经_面试经历—广州YY(欢聚时代) | 学步园
  18. 芒果改进YOLOv7系列:首发改进特征融合网络BiFPN结构,融合更多有效特征
  19. ant modal 修改样式 style
  20. 2计算机电源机,有人说电脑主机电源功率越大越好,2个知识告诉你这观点是片面的...

热门文章

  1. 华为鸿蒙事件真相揭秘,鸿蒙咋来的?华为董事长揭秘
  2. 首次 LFS 搭建全过程
  3. 结构方程模型amos中介效应与调节效应
  4. Markdown中在线编辑公式LaTex
  5. 黑白简约个人网页制作 大学生个人网页设计模板 学生个人博客网页成品 简单个人网站作品下载 静态HTML CSS个人网页作业源代码
  6. 太极root权限_太极免root框架
  7. 向工程腐化开炮|资源治理
  8. 如何提升 B站 等级?
  9. 怎么创建css样式表,怎样创建可反复使用的外部CSS样式表?
  10. 网站 内容更新 监控 php,网站状态监控方法,使用PHP轻松监控你的网站运行状态...