PDA

View Full Version : udp broadcasting on all network interfaces



mastupristi
3rd April 2016, 02:41
I need to broadcast an udp packet on all network interfaces, using only QIODevice methods (QIODevice::write() and no QUDPSocket::writeDatagram()), apart from connetcing and binding.

I also have upgraded to qt 5.6 due to qt bug #26538 (https://bugreports.qt.io/browse/QTBUG-26538).

A simplified example is as follows:

class Dialog : public QDialog
{
Q_OBJECT

public:
explicit Dialog(QWidget *parent = 0);
~Dialog();

private:
Ui::Dialog *ui;
QUdpSocket *sock;

public slots:
void onReadyRead(void);
void onButtonClicked(void);
};

#define USE_QIODEVICE // remove this define to use QUDPSocket methods

Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);

connect(ui->pushBtn,SIGNAL(clicked()), this, SLOT(onButtonClicked()));

qDebug() << qVersion();

sock = new QUdpSocket(this);
connect(sock, &QUdpSocket::readyRead, this, &Dialog::onReadyRead);

#if !defined(USE_QIODEVICE)
sock->bind();
qDebug() << sock->localPort();
#endif
}

void Dialog::onButtonClicked(void)
{
QList<QNetworkInterface> list = QNetworkInterface::allInterfaces();
foreach (QNetworkInterface iface, list)
{
QList<QNetworkAddressEntry> AEList = iface.addressEntries();
foreach(QNetworkAddressEntry AE, AEList)
{
if(AE.broadcast().protocol() == QAbstractSocket::IPv4Protocol)
{
#if defined(USE_QIODEVICE)
sock->bind(54366); // I have to call bind() for every interface because disconnectFromHost() unbind
sock->connectToHost(AE.broadcast(), 44444); // I have to call connectToHost to make the subsequent call to write() works
qDebug() << sock->localPort();
sock->write("hallo");
sock->disconnectFromHost(); // I have to call disconnectFromHost() otherwise the subsequent call to connectToHost() will fail
#else
QByteArray dg;
dg = "hallo";
sock->writeDatagram(dg,AE.broadcast(), 44444);
#endif
qDebug() << "send to" << AE.broadcast();
}
}
}
}

void Dialog::onReadyRead(void)
{
#if defined(USE_QIODEVICE)
QByteArray ba;

qDebug() << "received";
ba = sock->readAll();
#else
char data[64];
QHostAddress host;
quint16 port;

while(sock->hasPendingDatagrams())
{
sock->readDatagram(data, sizeof(data), &host, &port);
qDebug() << "received";
}

#endif
// data processing...
}


Unfortunately this code doesn't works because when the replies come back from the hosts the interface is (probably already) unbound, and onReadyRead() is never called.

When I remove definition of USE_QIODEVICE the QUDPSocket methods are used instead of QIODevice.
In this way all seems to work.

How can I get the wanted behaviour using only QIODevice methods (apart from connetcing and binding)?

Best regards
Max

anda_skoa
3rd April 2016, 10:12
Why do you want to have such complicated code as connecting and disconnceting when you made it work with the much simpler UDP specific API?

Cheers,
_

mastupristi
3rd April 2016, 14:56
I have to use QIODevice API, I cannot use UDP socket api directly

Maybe be above sample code is too simplified (for sake of simplicity). A more fitting example could be:

class Frame; // frames declaration. It represent data to be sent/receive

class FrameManagement: public QObject
{
Q_OBJECT
public:
FrameManagement(QIODevice *p, QObject *parent = 0);
~FrameManagement();

void sendFrame(const Frame &);

private:
QIODevice *m_iodevice;

private slots:
void processPendingData(void);

signals:
void frameReceived(Frame);
};

FrameManagement::FrameManagement(QIODevice *p, QObject *parent) :
m_iodevice(p),
QObject(parent)
{
if(NULL != m_iodevice)
connect(m_iodevice, &QIODevice::readyRead, this, &FrameManagement::processPendingData);

}

FrameManagement::sendFrame(const Frame &frame)
{
QByteArray ba;

ba = frame.toByteArray(); // a frame can be converted in a QByteArray

/*
* here I use QIODevice::write(), and cannot use UDP api, because is supposed I don't know that m_iodevice is a QUdpSocket
* It could be QTcpSocket, or QSerialPort
*/
m_iodevice->write(ba);
}


void FrameManagement::processPendingData(void)
{
QByteArray ba;

qDebug() << "received";
/*
* here I use QIODevice::readAll(), and cannot use UDP api, because is supposed I don't know that m_iodevice is a QUdpSocket
* It could be QTcpSocket, or QSerialPort
*/
ba = sock->readAll();

Frame frame;

frame.fromByteArray(ba); // A ByteArray can be validated and converted in Frame

if(frame.isValid())
emit frameReceived(frame);
}


class Dialog : public QDialog
{
Q_OBJECT

public:
explicit Dialog(QWidget *parent = 0);
~Dialog();

private:
Ui::Dialog *ui;
QUdpSocket *sock;
FrameManagement *framemanagement;


public slots:
void onFrameArrived(Frame);
void onButtonClicked(void);
};


Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);

connect(ui->pushBtn,SIGNAL(clicked()), this, SLOT(onButtonClicked()));

qDebug() << qVersion();

sock = new QUdpSocket(this);
connect(sock, &QUdpSocket::readyRead, this, &Dialog::onReadyRead);

framemanagement = new FrameManagement(sock, this);
connect(framemanagement,SIGNAL(frameReceived(Frame )), this, SLOT(onFrameArrived(Frame)));
}

void Dialog::onButtonClicked(void)
{
Frame frame;
// initiaalize frame

QList<QNetworkInterface> list = QNetworkInterface::allInterfaces();
foreach (QNetworkInterface iface, list)
{
QList<QNetworkAddressEntry> AEList = iface.addressEntries();
foreach(QNetworkAddressEntry AE, AEList)
{
if(AE.broadcast().protocol() == QAbstractSocket::IPv4Protocol)
{
sock->bind(54366); // I have to call bind() for every interface because disconnectFromHost() unbind
sock->connectToHost(AE.broadcast(), 44444); // I have to call connectToHost to make the subsequent call to write() works
qDebug() << sock->localPort();
framemanagement->sendFrame(frame);
sock->disconnectFromHost(); // I have to call disconnectFromHost() otherwise the subsequent call to connectToHost() will fail
qDebug() << "send to" << AE.broadcast();
}
}
}
}

void Dialog::onFrameArrived(Frame frame)
{
// suppose I have overloaded operator<< for Frame
qDebug() << "frame arrived" << frame;

// frame elaboration
// ...
}


As you can see che code does not direct call the read() and write() api. It do throug FrameManagement class, that doesn't know which kind of device is (QUdpSocket, QTcpSocket, QSerialPort or some others...)
Infacts all datails about the actual nature of Device (QUdpSocket) is in the Dialog class, that setup the QIODevice in the right way so that FrameManagement can safetly call readAll() and write() methods.

I wonder if I need one UDP socket (and a FrameManagement) for every network interface, in this way every socket can be bound and connected only once, and I can connect FrameManagement::frameReceived() of every object to onFrameArrived().
This should works, but I wonder if there is a more smart manner to do using only one socket as in the previous example where, commenting out
#define USE_QIODEVICE the method QUdpSocket::writeDatagram() is used

best regards
max

anda_skoa
3rd April 2016, 16:25
Hmm, I see.

Maybe you are abstracting too early then.
If you had an abstract frame management that would then be implemented depending on the connection type, then the respective specialization could make use of whatever underlying API it has available.

In any case the constant conncet/disconnect looks terrible, have you considered having one device per "connection".

If you really need to use a single device, make sure you actually wait for it to complete each task, e.g. wait for it to be connected, wait for it to have sent the data, wait for it to disconnect.
Obviously you will never be able to recieve any response, since you never keep any port bound. but maybe that's ok for your use case.

Cheers,
_

mastupristi
4th April 2016, 01:48
Unfortunately it seems it cannot work.
When I call QUdpSocket::connectToHost() the connection is established
To test this I wrote another example:
11847


#include <QDialog>
#include <QUdpSocket>

namespace Ui {
class Dialog;
}

class Dialog : public QDialog
{
Q_OBJECT

public:
explicit Dialog(QWidget *parent = 0);
~Dialog();

private:
Ui::Dialog *ui;

QUdpSocket *sock_uni_udp;
QUdpSocket *sock_uni_qio;
QUdpSocket *sock_broad_udp;
QUdpSocket *sock_broad_qio;

public slots:
void recv_uni_udp(void);
void recv_uni_qio(void);
void recv_broad_udp(void);
void recv_broad_qio(void);

void btn_uni_udp(void);
void btn_uni_qio(void);
void btn_broad_udp(void);
void btn_broad_qio(void);
};


#define UNICASTADDR "192.168.155.1"
#define BROADCASTADDR "192.168.155.255"

Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);

sock_uni_udp = new QUdpSocket(this);
sock_uni_qio = new QUdpSocket(this);
sock_broad_udp = new QUdpSocket(this);
sock_broad_qio = new QUdpSocket(this);

QString str;
QTextStream txtstream(&str);

sock_uni_udp->bind();
str.clear();
txtstream << "bound to " << sock_uni_udp->localPort();
ui->uni_udp_port->setText(str);
connect(sock_uni_udp, SIGNAL(readyRead()),this, SLOT(recv_uni_udp()));

sock_broad_udp->bind();
str.clear();
txtstream << "bound to " << sock_broad_udp->localPort();
ui->broad_udp_port->setText(str);
connect(sock_broad_udp, SIGNAL(readyRead()),this, SLOT(recv_broad_udp()));

sock_broad_qio->bind();
sock_broad_qio->connectToHost(QHostAddress(QString(BROADCASTADDR)) ,44444);
str.clear();
txtstream << "bound to " << sock_broad_qio->localPort();
ui->broad_qio_port->setText(str);
connect(sock_broad_qio, SIGNAL(readyRead()),this, SLOT(recv_broad_qio()));

sock_uni_qio->bind();
sock_uni_qio->connectToHost(QHostAddress(QString(UNICASTADDR)),4 4444);
str.clear();
txtstream << "bound to " << sock_uni_qio->localPort();
ui->uni_qio_port->setText(str);
connect(sock_uni_qio, SIGNAL(readyRead()),this, SLOT(recv_uni_qio()));


connect(ui->uni_udp_api_btn, SIGNAL(clicked()),this,SLOT(btn_uni_udp()));
connect(ui->uni_qio_api_btn, SIGNAL(clicked()),this,SLOT(btn_uni_qio()));
connect(ui->broad_udp_api_btn, SIGNAL(clicked()),this,SLOT(btn_broad_udp()));
connect(ui->broad_qio_api_btn, SIGNAL(clicked()),this,SLOT(btn_broad_qio()));
}

Dialog::~Dialog()
{
delete sock_uni_udp;
delete sock_uni_qio;
delete sock_broad_udp;
delete sock_broad_qio;

delete ui;
}

void Dialog::btn_uni_udp(void)
{
sock_uni_udp->writeDatagram("hallo",5, QHostAddress(QString(UNICASTADDR)),44444);
}

void Dialog::btn_uni_qio(void)
{
sock_uni_qio->write("hallo");
}

void Dialog::btn_broad_udp(void)
{
sock_broad_udp->writeDatagram("hallo",5, QHostAddress(QString(BROADCASTADDR)),44444);
}

void Dialog::btn_broad_qio(void)
{
sock_broad_qio->write("hallo");
}


void Dialog::recv_uni_udp(void)
{
char str[5];
sock_uni_udp->readDatagram(str, 5);
ui->uni_udp_recv->setText("arrived");
}

void Dialog::recv_uni_qio(void)
{
QByteArray ba;
ba = sock_uni_udp->readAll();
ui->uni_qio_recv->setText("arrived");

}

void Dialog::recv_broad_udp(void)
{
char str[5];
sock_broad_udp->readDatagram(str, 5);
ui->broad_udp_recv->setText("arrived");
}

void Dialog::recv_broad_qio(void)
{
QByteArray ba;
ba = sock_broad_qio->readAll();
ui->broad_qio_recv->setText("arrived");
}


In this example I test all four combination: QIODevice API broadcast and unicast, QUdpSochet API broadcast and unicast
in the contructor all sockets are bound and the window is:
http://www.qtcentre.org/attachment.php?attachmentid=11848&d=1459725652

and netstat output (only the entry involved) is:

max@kinets-dev:~$ netstat -nupa
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
udp 0 0 192.168.155.100:53527 192.168.155.1:44444 ESTABLISHED 4405/test-broadcast
udp 0 0 192.168.155.100:50986 192.168.155.255:44444 ESTABLISHED 4405/test-broadcast
udp6 0 0 :::50762 :::* 4405/test-broadcast
udp6 0 0 :::42704 :::* 4405/test-broadcast

In the first two lines I used connectToHost() api and the connection state is established. The only one that cannot receive udp datagrams is the second. My thought is that since the connection is established it can receive packet only from 192.168.155.255, but when the peer reply it use its own unicast address so the packet is discarded.
This is confirmed also by whireshark:
http://www.qtcentre.org/attachment.php?attachmentid=11849&d=1459726679

So the question now is: Is possible and how to send in broadcast but being able to receive replies using QIODevice API?

best regards
max

anda_skoa
4th April 2016, 09:37
I guess at this point the best way is to check the code.
Maybe connectToHost() calls bind again or unbinds, etc.

But you should ask yourself if this is really worth it if you can just simply use the API that you know to work and abstract yourself.

Cheers,
_

mastupristi
4th April 2016, 16:10
yes

now I try to use upd api directly

best regards
max

Aleksei
8th April 2016, 15:36
You can use decorator pattern. Decorate QIODevice and use UDP API inside it.

Regards,
Aleksei

cubansecret
11th April 2016, 14:57
This is the code I came up to be run in a console for broadcasting using a single ip:




// myudp.h
#ifndef MYUDP_H
#define MYUDP_H

#include <QObject>
#include <QUdpSocket>

class MyUDP : public QObject
{
Q_OBJECT
public:
explicit MyUDP(QObject *parent = 0);
void HelloUDP();
signals:

public slots:
void readyRead();

private:
QUdpSocket *socket;

};

#endif // MYUDP_H
//end of myudp.h




// myudp.cpp
#include "myudp.h"

#define BROADCAST_IP "192.168.2.210"

MyUDP::MyUDP(QObject *parent) :
QObject(parent)
{
// create a QUDP socket
socket = new QUdpSocket(this);


// The most common way to use QUdpSocket class is
// to bind to an address and port using bind()
// bool QAbstractSocket::bind(const QHostAddress & address,
// quint16 port = 0, BindMode mode = DefaultForPlatform)

//socket->bind(QHostAddress::LocalHost, 1234);
socket->bind(QHostAddress(QString(BROADCAST_IP)),1234);

connect(socket, SIGNAL(readyRead()),this, SLOT(readyRead()));
}

void MyUDP::HelloUDP()
{
QByteArray Data;
Data.append("This message has been broadcasted ;)");

// Sends the datagram datagram
// to the host address and at port.

//socket->writeDatagram(Data, QHostAddress::LocalHost, 1234);
socket->writeDatagram(Data, QHostAddress(QString(BROADCAST_IP)),1234);
}

void MyUDP::readyRead()
{
// when data comes in
QByteArray buffer;
buffer.resize(socket->pendingDatagramSize());

QHostAddress sender;
quint16 senderPort;

// qint64 QUdpSocket::readDatagram(char * data, qint64 maxSize,
// QHostAddress * address = 0, quint16 * port = 0)
// Receives a datagram no larger than maxSize bytes and stores it in data.
// The sender's host address and port is stored in *address and *port
// (unless the pointers are 0).

socket->readDatagram(buffer.data(), buffer.size(),
&sender, &senderPort);

qDebug() << "Message from: " << sender.toString();
qDebug() << "Message port: " << senderPort;
qDebug() << "Message: " << buffer;
}
// end of myudp.cpp



// main.cpp
#include <QCoreApplication>
#include "myudp.h"

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

MyUDP client;

client.HelloUDP();

return a.exec();
}
// end of main.cpp