1

Here is a short UDP server example in Qt below which does work but what I don't like is that I'm polling to see if new data is available. I've come across some examples of a readyRead() but they all seem to introduce a qt class. Do I need to use a qt class in order to take advantage of the readyRead() signal?

Here is the working but simple UDP server implemented entirely in main:

#include <QDebug>
#include <QUdpSocket>
#include <QThread>

int main(int argc, char *argv[])
{
    QUdpSocket *socket = new QUdpSocket();
    u_int16_t port = 7777;

    bool bindSuccess =  socket->bind(QHostAddress::AnyIPv4, port);
    if (!bindSuccess) {
        qDebug() << "Error binding to port " << port << " on local IPs";
        return a.exec();
    }
    qDebug() << "Started UDP Server on " << port << endl;

    QHostAddress sender;
    while (true) {
        while (socket->hasPendingDatagrams()) {
            QByteArray datagram;
            datagram.resize(socket->pendingDatagramSize());
            socket->readDatagram(datagram.data(),datagram.size(),&sender,&port);
            qDebug() << "Message From :: " << sender.toString();
            qDebug() << "Port From :: "<< port;
            qDebug() << "Message :: " << datagram.data();
        }
        QThread::msleep(20);
    }

    return 0;
}

Here is an example of the readyRead() signal: https://www.bogotobogo.com/Qt/Qt5_QUdpSocket.php

I haven't really figured out how to get this to work yet. I must be doing something wrong. Here is the UDP connection code i'm trying:

#include "myudp.h"

MyUDP::MyUDP(QObject *parent) : QObject(parent) {
}

void MyUDP::initSocket(u_int16_t p) {
    port = p;

    udpSocket = new QUdpSocket(this);
    bool bindSuccess = udpSocket->bind(QHostAddress::LocalHost, port);
    if (!bindSuccess) {
        qDebug() << "Error binding to port " << port << " on local IPs";
        return;
    }
    connect(udpSocket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams()));
}

void MyUDP::readPendingDatagrams() {
    QHostAddress sender;
    while (udpSocket->hasPendingDatagrams()) {
        QByteArray datagram;
        datagram.resize(udpSocket->pendingDatagramSize());
        udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &port);
        qDebug() << "Message From :: " << sender.toString();
        qDebug() << "Port From :: " << port;
        qDebug() << "Message :: " << datagram.data();
    }
}

myudp.h

#include <QObject>
#include <QUdpSocket>

class MyUDP : public QObject
{
    Q_OBJECT
public:
    explicit MyUDP(QObject *parent);
    void initSocket(u_int16_t p);

    u_int16_t port;
    QUdpSocket *udpSocket;

signals:

public slots:
    void readPendingDatagrams();
};

new main.cpp

int main(int argc, char *argv[]) 
{
    MyUDP *myUDP = new MyUDP(0);
    myUDP->initSocket(port);

    while (true) {
        usleep(1000);
    }

    return 0;
}

I am testing with:

netcat 127.0.0.1 -u 7777
{"cid"="0x1234123412341", "fill_level"=3245 }<cr>
simgineer
  • 1,754
  • 2
  • 22
  • 49
  • Of course no custom classes are needed: connect to a lambda! – Kuba hasn't forgotten Monica Jan 16 '19 at 14:04
  • @KubaOber if I used a lambda, do you know what the third argument in the connect(..., ..., ?, ...) method should be? I've assumed the connect should still be used to get a non polling thread sleep solution. Would you be willing to share an answer with a lambda example? – simgineer Jan 16 '19 at 19:51
  • 1
    https://stackoverflow.com/q/19719397/1329652 – Kuba hasn't forgotten Monica Jan 16 '19 at 19:53
  • @KubaOber The build that I am porting does not use qmake. Is there a way to handle have an async notification without utilizing a QObject. In your example from Nov 1, 2013 you indicate that it is important to have the receiver (this) argument. I guess where I don't follow is that this usually refers to a qobject and if I'm not using qmake/moc to process the Q_OBJECT macro can we get away without inheriting QObject? Can I just pass a "nullptr" instead of "this" in the connect call? connect(a, SIGNAL(readyRead()), nullptr, SLOT(some lambda or method)) – simgineer Jan 18 '19 at 23:15
  • Ah, another XY problem, then. Using qmake is not relevant. Whatever build tool you use should be flexible enough to add build rules. You need to be able to use the source processing tools provided by Qt. That’s all. Perhaps you should ask how to adapt your build tool for Qt instead. The code that doesn’t use the `Q_OBJECT` macro is not guaranteed to work. The second `this` is absolutely necessary for correct thread context, and to disconnect the connection should the receiving object die. But it’s all moot: you are setting yourself up for a sore failure by not using moc etc. Don’t do that. – Kuba hasn't forgotten Monica Jan 21 '19 at 14:10

2 Answers2

1

What you're doing wrong is that you're not letting Qt's event loop run. i.e. this is incorrect:

int main(int argc, char *argv[]) 
{
   MyUDP *myUDP = new MyUDP(0);
   myUDP->initSocket(port);

   while (true) {
       usleep(1000);
   }

   return 0;
}

... instead, you should have something like this:

int main(int argc, char *argv[]) 
{
   QApplication app(argc, argv);

   // connect needs to occur after QCoreApplication declaration
   MyUDP *myUDP = new MyUDP(0);
   myUDP->initSocket(port);

   return app.exec();
}

... it is inside the app.exec() call where a Qt application spends most of its time (app.exec() won't return until Qt wants to quit), and there is where Qt will handle your UDP socket's I/O and signaling needs.

simgineer
  • 1,754
  • 2
  • 22
  • 49
Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
  • I revised my main so that it is similar to your second snipped with QApplication app(...); return app.exe(); but I still am not able to stimulate any behavior when I send any data with netcat to my UDP port. Do you have any other thoughts on what I'm missing? Really really appreciate your advise. Do you have any thoughts on whether the bind should come before or after the connect? – simgineer Jan 16 '19 at 03:53
  • I don't think it makes any difference whether bind comes before or after connect. – Jeremy Friesner Jan 16 '19 at 04:14
  • FWIW I am able to compile your program (with my suggested changes to main()) and get it to receive UDP packets. One other suggestion I have would be to change QHostAddress::LocalHost in your bind() call to QHostAddress::Any, that way your UDP socket can receive UDP packets from any network device and not only the loopback device. – Jeremy Friesner Jan 16 '19 at 04:21
  • I am developing on Ubuntu 18.04 with Qt5.9.5. Are you by chance using a linux as well? Trying to under stand what is off in my configuration. – simgineer Jan 16 '19 at 23:29
  • I'm using MacOS/X, but in my experience MacOS/X and Linux are quite similar as far as sockets are concerned. – Jeremy Friesner Jan 16 '19 at 23:30
  • found the issue and modified your post to capture that. Looks like the declaration of the QCoreApplication needs to happen before the connect and then followed by app.exec(). – simgineer Jan 18 '19 at 00:07
1

Please modify your processPendingDatagrams like this, to let newer incoming data be processed:

void MyUDP::readPendingDatagrams() {
    QHostAddress sender;
    uint16_t port;
    QByteArray datagram; // moved here

    while (udpSocket->hasPendingDatagrams()) {
        //QByteArray datagram; // you don't need this here
        datagram.resize(udpSocket->pendingDatagramSize());
        udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &port);
        qDebug() << "Message From :: " << sender.toString();
        qDebug() << "Port From :: " << port;
        qDebug() << "Message :: " << datagram.data();
    }

    // God knows why, there is always one more "dummy" readDatagram call to make, 
    // otherwise no new readyRead() will be emitted, and this function would never be called again
    datagram.resize(udpSocket->pendingDatagramSize());
    socket->readDatagram(datagram.data(),datagram.size(),&sender,&port);
}
  • Thank you for suggestion, It's been a while since I have been working on this project and I ended up creating classes which I used to link into their update signal to process received data. – simgineer Oct 16 '22 at 16:48