Solved QUdpSockets and Windows 10
-
Hi all -
So, I've been working on a small app that communicates with an embedded device via UDP multicast sockets. Testing this is complicated by the fact that my development system (Windows 10) has multiple NICs, and there doesn't seem to be any way to force it to choose one vs. the other, so my sockets often find themselves on the wrong network.
Today, I discovered the setMulticastInterface() function, and thought I'd try it. Unfortunately, while the sends all show as successful, but nothing shows up on Wireshark. Here's my code; maybe someone can see something I'm doing wrong:
(UdpSocket is a class I created)
QNetworkInterface UdpSocket::findNetworkInterface() { QNetworkInterface qni; QList<QNetworkInterface> qnil; QList<QNetworkInterface>::iterator it; Message msg; string str; qnil = QNetworkInterface::allInterfaces(); for (it = qnil.begin(); it != qnil.end(); ++it) { m_sock = new QUdpSocket(this); m_sock->bind(m_addrRecv, MCAST_PORT, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint); m_sock->setMulticastInterface(qni); m_sock->setSocketOption(QAbstractSocket::MulticastLoopbackOption, 0); msg.buildDiscoveryRequest(); str = msg.encodeXml(); send(str); recv(); m_sock->close(); delete m_sock; } return qni; }
Thanks...
-
see https://doc.qt.io/qt-5/qtnetwork-programming.html
"... most of its functions work asynchronously ..."
-
Yeah, I'm aware of that (and wouldn't leave the code like that forever), but "asynchronous" doesn't mean "never." And I'm never seeing anything (though Wireshark could be part of the culprit here).
-
Another point...when I call this function:
qnil = QNetworkInterface::allInterfaces();
I get these error messages:
Invalid parameter passed to C runtime function. Invalid parameter passed to C runtime function. Invalid parameter passed to C runtime function. Invalid parameter passed to C runtime function. Invalid parameter passed to C runtime function. Invalid parameter passed to C runtime function.
I don't know that it's related to my problem, but I thought I'd pass it on.
-
@mzimmers said in QUdpSockets and Windows 10:
doesn't mean "never."
You immediately delete your socket so yes - it's never ...
-
Oh, good point. So, do I need to return control to the event loop before I close and delete the socket(s)?
-
@mzimmers you should delete the socket only after it has finished sending the packets. This should work-
connect(m_sock, &QUdpSocket::bytesWritten, m_sock, &QObject::deleteLater)
-
@shaan7 thanks for that. I need to determine which interface in the list is the one I should use. When I write to the "correct" interface, I'll get a response on the socket. Probably won't on the other.
It appears that I can't do this in a loop, since the read signal doesn't get delivered. How best to know which of my attempts was the successful one?
-
Hi,
You can create a QMap that contains that information and look it up before deleting the socket.
-
@SGaist I don't follow you...can you give a little more detail?
-
QMap<QUdpSocket *, QNetworkInterface> _socketInterfaceMap;
Then in your loop you can store the relation between the socket and the network interface it sent data to.
-
I must be extra-dense today; I'm still not connecting the dots here.
So, for each item in the interface list, I create a socket, bind, connect, and send out an inquiry. I get a response from one of them, signalling my read() slot. How do I know which socket sent the readyRead()?
-
You use the object oriented modularity breaker QObject::sender method.
-
@SGaist OK, so sender() returns an object (pointer). How do I translate that into a QUdpSocket pointer to use as a key in my QMap?
-
qobject_cast is your friend.
-
OK...the sends are working, but the recv() slot isn't getting triggered. I wonder if it's due to my reuse of the slot pointer here:
qnil = QNetworkInterface::allInterfaces(); for (it = qnil.begin(); it != qnil.end(); ++it) { sock = new QUdpSocket(this); m_socketInterfaceMap.insert(sock, *it); qDebug() << it->humanReadableName(); QObject::connect(sock, &QUdpSocket::readyRead, this, &UdpSocket::recv); sock->bind(m_addrRecv, MCAST_PORT, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint); sock->setMulticastInterface(*it); sock->setSocketOption(QAbstractSocket::MulticastLoopbackOption, 0); m_sock = sock; send(str); } return;
I thought this would be legit, but...should I have a vector of these sockets instead of reusing the pointer?
-
Why are you connecting the readyRead signal ? Didn't you want to use bytesWritten ?
-
No, I want the signal for when the remote device responds to my send.
I realize I was making the code unnecessarily complicated; here's my current version:
// create the socket, bind to it and set some options. m_sock = new QUdpSocket(this); rc = m_sock->bind(m_addrRecv, MCAST_PORT, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint); if (rc == false) { cout << m_sock->errorString().toStdString() << endl; exit(1); } m_sock->setSocketOption(QAbstractSocket::MulticastLoopbackOption, 0); // join multicast group. rc = m_sock->joinMulticastGroup(m_addrSend); if (rc == false) { cout << m_sock->errorString().toStdString() << endl; exit(1); } QObject::connect(m_sock, &QUdpSocket::readyRead, this, &UdpSocket::recv); QObject::connect(m_sock, &QUdpSocket::disconnected, this, &UdpSocket::reconnect); qnil = QNetworkInterface::allInterfaces(); for (it = qnil.begin(); it != qnil.end(); ++it) { type = it->type(); if (type == QNetworkInterface::Ethernet) { //m_socketInterfaceMap.insert(m_sock, *it); qDebug() << it->humanReadableName() << type; m_sock->setMulticastInterface(*it); send(str); } }
My concern now is that I'm not getting any hit on my recv() slot. Once I get that working, I can work out the matter of determining which interface was used, using the technique you described above. EDIT: I simplified it further, by only writing to the interface I know is correct, and I still don't get a recv() trigger. I think I must have messed up something in my multicast setup.
EDIT 2: I found the problem -- evidently, even if you choose an interface for a socket, when you call joinMulticastGroup, you still have to specify the interface, or the OS will choose one for you. With the change below, I'm now getting my recv() slot.
for (it = qnil.begin(); it != qnil.end(); ++it) { type = it->type(); // if (type == QNetworkInterface::Ethernet) if (it->humanReadableName() == "Ethernet 3") { //m_socketInterfaceMap.insert(m_sock, *it); qDebug() << it->humanReadableName() << type; m_sock->setMulticastInterface(*it); // join multicast group. rc = m_sock->joinMulticastGroup(m_addrSend, *it);
Now I just have to use that stuff that SGaist suggested above to determine which interface supplied the response.