Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. General talk
  3. Brainstorm
  4. I'm doing it wrong...again

I'm doing it wrong...again

Scheduled Pinned Locked Moved Solved Brainstorm
12 Posts 3 Posters 2.4k 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 Offline
    mzimmersM Offline
    mzimmers
    wrote on last edited by
    #1

    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 1 Reply Last reply
    0
    • 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 Extensions
                          • Unsolved