Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. General talk
  3. Brainstorm
  4. I'm doing it wrong...again
Qt 6.11 is out! See what's new in the release blog

I'm doing it wrong...again

Scheduled Pinned Locked Moved Solved Brainstorm
12 Posts 3 Posters 4.1k Views 4 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • mzimmersM mzimmers

    Hi all -

    I have another thread going on this topic, but that's more of a how-to. I think I need a lesson in Qt design.

    I'm writing an app that communicates with a remote device via UDP sockets. The rub is that the host system may have multiple network interfaces, and the program needs to determine the correct one to use. My method for this seemed simple enough at first: get a list of the interfaces, try each one, and select the one that responds appropriately.

    The asynchronous nature of Qt socket reads/writes has complicated this, though. I'd like to run a routine at startup (from the c'tor, even) that does this, but all I can do is the sends; the reads will be blocked until I exit my startup routine. How do I get around this?

    Thanks...

    kshegunovK Offline
    kshegunovK Offline
    kshegunov
    Moderators
    wrote on last edited by
    #2

    @mzimmers said in I'm doing it wrong...again:

    I'd like to run a routine at startup (from the c'tor, even) that does this, but all I can do is the sends; the reads will be blocked until I exit my startup routine. How do I get around this?

    Use a queued call. QMetaObject::invokeMethod with Qt::QueuedConnection is your friend in these situations. You can queue the sends until after your init goes (as it may not be appropriate in the constructors), and receive the replies in a (private) slot where you determine which one is the valid interface.

    As a side note, have you considered the case where you may get multiple responses (i.e. interfaces) and thus need to handle this separately after? Is it possible or significant?

    Read and abide by the Qt Code of Conduct

    1 Reply Last reply
    5
    • mzimmersM Offline
      mzimmersM Offline
      mzimmers
      wrote on last edited by mzimmers
      #3

      Are you suggesting I queue my sends? Not sure I understand how this would work.

      I was thinking that I'd try one interface, give it some period of time (<1 second) to return something valid, then move onto the next interface. If none work out, I'd give the user a message and terminate. Are you suggesting that I do this in parallel instead -- sending on all the interfaces, and then waiting for a response from any of them?

      EDIT: even though I don't fully understand this, I decided to experiment a little with it. I don't seem to be properly forming the call to invokeMethod():

      QUdpSocket *m_sock = nullptr;
      ...
      qint64 UdpSocket::send(string str) {...}
      ...
      qint64 bytesSent;
      rc = QMetaObject::invokeMethod(m_sock, "send", Qt::QueuedConnection,
                                     Q_RETURN_ARG(qint64, bytesSent),
                                     Q_ARG(std::string, str));
      
      

      Can anyone see what I'm doing wrong here? I'm getting the error message: "QMetaObject::invokeMethod: No such method QUdpSocket::send(std::string)"

      kshegunovK 1 Reply Last reply
      0
      • SGaistS Offline
        SGaistS Offline
        SGaist
        Lifetime Qt Champion
        wrote on last edited by
        #4

        Hi,

        From the looks of it, you didn't make that method Q_INVOKABLE.

        Interested in AI ? www.idiap.ch
        Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

        1 Reply Last reply
        2
        • mzimmersM Offline
          mzimmersM Offline
          mzimmers
          wrote on last edited by
          #5

          No, I didn't. (I'd never seen that before.) Unfortunately, that didn't change the outcome -- I'm still getting the error message.

          kshegunovK 1 Reply Last reply
          0
          • mzimmersM mzimmers

            Are you suggesting I queue my sends? Not sure I understand how this would work.

            I was thinking that I'd try one interface, give it some period of time (<1 second) to return something valid, then move onto the next interface. If none work out, I'd give the user a message and terminate. Are you suggesting that I do this in parallel instead -- sending on all the interfaces, and then waiting for a response from any of them?

            EDIT: even though I don't fully understand this, I decided to experiment a little with it. I don't seem to be properly forming the call to invokeMethod():

            QUdpSocket *m_sock = nullptr;
            ...
            qint64 UdpSocket::send(string str) {...}
            ...
            qint64 bytesSent;
            rc = QMetaObject::invokeMethod(m_sock, "send", Qt::QueuedConnection,
                                           Q_RETURN_ARG(qint64, bytesSent),
                                           Q_ARG(std::string, str));
            
            

            Can anyone see what I'm doing wrong here? I'm getting the error message: "QMetaObject::invokeMethod: No such method QUdpSocket::send(std::string)"

            kshegunovK Offline
            kshegunovK Offline
            kshegunov
            Moderators
            wrote on last edited by kshegunov
            #6

            Sorry I wrote that in passing as I was leaving work. I could've and probably should've elaborated more.

            @mzimmers said in I'm doing it wrong...again:

            Are you suggesting I queue my sends? Not sure I understand how this would work.

            No, not the sends themselves, they're already async. I meant that the constructor may not be the best place to do them (depending on your code), so if you want to postpone them for after the constructor you can do that through QMetaObject::invokeMethod. I had such a case today, where I needed to call a virtual method, but the constructor is less than an ideal place for that to happen ...

            I was thinking that I'd try one interface, give it some period of time (<1 second) to return something valid, then move onto the next interface. If none work out, I'd give the user a message and terminate. Are you suggesting that I do this in parallel instead -- sending on all the interfaces, and then waiting for a response from any of them?

            Very much so, yes. The other way can get very latent for no good reason. Say you have 3 interfaces configured and want to try them sequentially, this means waiting for 2 seconds + the response time. If you do that in parallel it's only the response time and/or a timeout (depending on the answers to the questions from my previous post). I was thinking more - collect the responses in a slot and decide what to do with them thereafter. Something like (pseudocode):

            int interfacesNumber;
            UdpSocket * sockets = new UdpSocket[interfacesNumber];
            for (...)  {
                // Configure and send here
                // Collect responses in a slot
                QObject::connect(udpSocket, &QUdpSocket::datagramPending, this, std::bind(&MyClass::handleResponder, this, udpSocket));
                QObject::connect(&timeoutTimer, &QTimer::timeout, udpSocket, &QObject::deleteLater);
            }
            

            Where the slot could be something like:

            void MyClass::handleResponder(QUdpSocket * socket)
            {
                QObject::disconnect(&timeoutTimer, nullptr, socket, nullptr); //< Disconnected everything so it doesn't get destroyed later
                // Save the socket for later ...
            }
            

            Read and abide by the Qt Code of Conduct

            1 Reply Last reply
            1
            • mzimmersM mzimmers

              No, I didn't. (I'd never seen that before.) Unfortunately, that didn't change the outcome -- I'm still getting the error message.

              kshegunovK Offline
              kshegunovK Offline
              kshegunov
              Moderators
              wrote on last edited by
              #7

              @mzimmers said in I'm doing it wrong...again:

              I'm still getting the error message.

              You can't return (values) from queued connections, it breaks determinism and as such is not allowed.

              Read and abide by the Qt Code of Conduct

              1 Reply Last reply
              1
              • mzimmersM Offline
                mzimmersM Offline
                mzimmers
                wrote on last edited by
                #8

                I removed the return argument and changed the type to void. Still getting the error. Is passing arguments permitted?

                kshegunovK 1 Reply Last reply
                0
                • mzimmersM mzimmers

                  I removed the return argument and changed the type to void. Still getting the error. Is passing arguments permitted?

                  kshegunovK Offline
                  kshegunovK Offline
                  kshegunov
                  Moderators
                  wrote on last edited by kshegunov
                  #9

                  @mzimmers said in I'm doing it wrong...again:

                  Still getting the error. Is passing arguments permitted?

                  Yes, if they're declared and registered as metatypes (which also implies public default constructor, public copy constructor and destructor). See Q_DECLARE_METATYPE and qRegisterMetaType in the docs for the full picture.

                  Read and abide by the Qt Code of Conduct

                  1 Reply Last reply
                  0
                  • mzimmersM Offline
                    mzimmersM Offline
                    mzimmers
                    wrote on last edited by
                    #10

                    Hmm...I'm already registering it (in main):

                        qRegisterMetaType<std::string>("std::string");
                    

                    I added this in my header file:

                        Q_DECLARE_METATYPE(std::string)
                    

                    Is this not correct?

                    kshegunovK 1 Reply Last reply
                    0
                    • mzimmersM mzimmers

                      Hmm...I'm already registering it (in main):

                          qRegisterMetaType<std::string>("std::string");
                      

                      I added this in my header file:

                          Q_DECLARE_METATYPE(std::string)
                      

                      Is this not correct?

                      kshegunovK Offline
                      kshegunovK Offline
                      kshegunov
                      Moderators
                      wrote on last edited by kshegunov
                      #11

                      Yes, for most cases. It's correct, but the alias you provided isn't matching exactly. You do this:

                      qRegisterMetaType<std::string>("std::string");
                      

                      But the function prototype spells:

                      qint64 UdpSocket::send(string str) {...}
                      

                      So string is unknown. If you expand the namespace, then you need to give the unqualified class name as a string argument. So it'd be like this in your case:

                      qRegisterMetaType<std::string>("string");
                      

                      Also make sure you object really has this method, as I see one m_sock being QUdpSocket, while the function prototype is for UdpSocket.

                      Read and abide by the Qt Code of Conduct

                      1 Reply Last reply
                      2
                      • mzimmersM Offline
                        mzimmersM Offline
                        mzimmers
                        wrote on last edited by
                        #12

                        Yep...I should have specified "this" as the first argument in the invokeMethod() call. Seems to be working now. Here are some snippets:

                         UdpSocket::UdpSocket(QObject *parent) : QObject(parent)
                        {
                            bool rc;
                            // set up addresses.
                            m_addrRecv.setAddress(QHostAddress::AnyIPv4);
                            m_addrSend.setAddress(MCAST_GROUP);
                        
                            rc = QMetaObject::invokeMethod(this, "sendDiscoveryMsgs", Qt::QueuedConnection);
                        }	
                        void UdpSocket::sendDiscoveryMsgs()
                        {
                           qnil = QNetworkInterface::allInterfaces();
                        
                            // for each viable interface, create and configure a socket.
                            for (it = qnil.begin(); it != qnil.end(); ++it)
                            {
                                sock = new QUdpSocket;
                                sock->bind(m_addrRecv, MCAST_PORT, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint);
                                QObject::connect(sock, &QUdpSocket::readyRead, this, &UdpSocket::checkResponse);
                                m_sock = sock; // the send() below uses m_sock.
                                send(str);
                            }
                        }
                        void UdpSocket::checkResponse()
                        {
                            int rc = recv();
                            if (rc == 0) // got a valid response
                            {
                                if (m_msgStr.find(MsgTypeText[MSG_DISCOVERY_ACK]) != string::npos)
                                {
                                    m_sock = qobject_cast<QUdpSocket *>(sender());
                                    m_qni = new QNetworkInterface;
                                    *m_qni = m_socketInterfaceMap[m_sock];
                                    QObject::disconnect(m_sock, &QUdpSocket::readyRead, this, &UdpSocket::checkResponse);
                                    QObject::connect(m_sock, &QUdpSocket::readyRead, this, &UdpSocket::recv);
                                    QObject::connect(m_sock, &QUdpSocket::disconnected, this, &UdpSocket::reconnect);
                                }
                            }
                        }
                        

                        I have a minor memory leak in that I don't destroy the unused sockets after I find the right one, but this way I don't have to maintain a list of the sockets while I'm trying to determine the "good" one.

                        Thanks for the help.

                        1 Reply Last reply
                        0

                        • Login

                        • Login or register to search.
                        • First post
                          Last post
                        0
                        • Categories
                        • Recent
                        • Tags
                        • Popular
                        • Users
                        • Groups
                        • Search
                        • Get Qt
                        • Unsolved