Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QUdpSocket Benchmark for Package Lost Ratio



  • When we planned to ship our app from linux to windows, we decided to use Qt5 because of its cross-platform benifit. QUdpSocket is wildly used in M2M scenario, however it seems that QUdpSocket is slower than platform local API in windows, that can cause many package lost.

    Test result

    We built a test program, with 100 packages/20ms, find these result in 2 PC (one is a notebook, the other is a workstation).

    lost ratio(lost:total) QUdpSocket with simple Loops QUdpSocket with signal-slots Local WinAPI
    notebook PC 277:10277 2482:12482 3:10003
    workstation PC 28:10028 247:10246 0:10000

    It seems that:

    • Reliability is directly related to CPU performance;
    • The packet loss of signal and slot is the most serious;
    • Even with a simple loop, it is significantly slower than the native API.

    Attachment - Test code

    • built it in msys2 mingw32 qt5.14 static

    project

    QT -= gui
    QT += network
    CONFIG += c++11 console
    CONFIG -= app_bundle
    QMAKE_LIBS += -lws2_32
    DEFINES += QT_DEPRECATED_WARNINGS
    SOURCES += \
    	main.cpp
    HEADERS += \
    	allinone.h
    

    main.cpp

    #include "allinone.h"
    
    int main(int argc, char *argv[])
    {
    	QCoreApplication a(argc, argv);
    	//Start Sending
    	sendingThread * tsend = new sendingThread;
    	tsend->start(QThread::HighestPriority);
    	QThread::msleep(3000);
    	printf("Start...\n");
    
    	//test UDP with a simple loop (while...)
    	int 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 Udp with a QObject Class , using Signals and Slots.
    	QThread * 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 Local windows API
    	const int local_total = local_test();
    	printf("Local Socket :\n\tSend %d, Recv %d, Lost %d.\n",
    							   local_total,
    							   tests,
    							   local_total-tests );
    	//end
    	tsend->stop();
    	tsend->wait();
    	tsend->deleteLater();
    	QCoreApplication::processEvents();
    	getchar();
    	return 0;
    }
    

    allinone.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;
    	if (p->bind(QHostAddress::LocalHost,port))
    	{
    		for (int i=0;i<tests;++i)
    		{
    			while (!(p->hasPendingDatagrams()))
    				p->waitForReadyRead(20);
    			QNetworkDatagram g = p->receiveDatagram();
    			if (g.data().size()<8)
    				continue;
    			const qint64 * d = reinterpret_cast<const qint64 *>(g.data().constData());
    			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(){
    		while (m_sock->hasPendingDatagrams())
    		{
    			QNetworkDatagram g = m_sock->receiveDatagram();
    			if (g.data().size()<8)	continue;
    			if (++m_total>tests)
    			{
    				m_sock->close();
    				m_finished = true;
    				QThread::currentThread()->quit();
    				break;
    			}
    			const qint64 * d = reinterpret_cast<const qint64 *>(g.data().constData());
    			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 wsa
    	const WORD sockVision = MAKEWORD(2,2);
    	WSADATA wsadata;
    	//sockets
    	SOCKET sock;
    	if( WSAStartup(sockVision,&wsadata) != 0 )
    	{
    		printf("WSA initial failed.\n");
    		return 0;
    	}
    
    	//Create sock
    	sock = 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:port
    	sin.sin_family = AF_INET;
    	sin.sin_port = htons(port);
    	sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//local host
    	if( 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 Winsock
    		iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    		if (iResult != NO_ERROR) {
    			wprintf(L"WSAStartup failed with error: %d\n", iResult);
    			return;
    		}
    
    		//---------------------------------------------
    		// Create a socket for sending data
    		SendSocket = 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 receiver
    		while(!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
    

  • Lifetime Qt Champion

    Sorry but I don't understand what you're trying to achieve - an udp datagram is sent directly to the underlying OS, see https://code.woboq.org/qt5/qtbase/src/network/socket/qnativesocketengine_win.cpp.html#1290 and https://code.woboq.org/qt5/qtbase/src/network/socket/qnativesocketengine_unix.cpp.html#_ZN26QNativeSocketEnginePrivate18nativeSendDatagramEPKcxRK15QIpPacketHeader

    And when you try to receive a datagram you're doing a blocked wait for 20ms so ...



  • @Christian-Ehrlicher said in QUdpSocket Benchmark and Advice against Package Lost Ratio:

    Sorry but I don't understand what you're trying to achieve - an udp datagram is sent directly to the underlying OS, see https://code.woboq.org/qt5/qtbase/src/network/socket/qnativesocketengine_win.cpp.html#1290 and https://code.woboq.org/qt5/qtbase/src/network/socket/qnativesocketengine_unix.cpp.html#_ZN26QNativeSocketEnginePrivate18nativeSendDatagramEPKcxRK15QIpPacketHeader

    And when you try to receive a datagram you're doing a blocked wait for 20ms so ...

    Ok, When I comment these 20ms code, It's ok, Thank you!

    Start...
    QUdpSocket LOOP:
            Send 10013, Recv 10000, Lost 13.
    Start...
    QUdpSocket Signal and Slots:
            Send 10245, Recv 10000, Lost 245.
    Start...
    Local Socket :
            Send 10000, Recv 10000, Lost 0.
    Finished sending. Closing socket.
    Exiting.
    
    

    Much better! The only problem is CPU usage is very high when there is no data recieved.



  • I tested again, avoiding memory re-allocate and extra-info (Source Address) can improve performance, It's OK!

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

    Output:

    Start...
    QUdpSocket LOOP:
            Send 10000, Recv 10000, Lost 0.
    Start...
    QUdpSocket Signal and Slots:
            Send 10000, Recv 10000, Lost 0.
    Start...
    Local Socket :
            Send 10000, Recv 10000, Lost 0.
    Finished sending. Closing socket.
    Exiting.
    
    
    

  • Lifetime Qt Champion

    There is no buffering of a udp datagram in Qt code - the only thing you can do is to increase the os udp buffer. But I don't see this as drawback of Qt - it's simply the nature of UDP.