Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QTcpSocket client, write problem...



  • I've got a server that I can connect to with a browser and it responds ok. Now I'm trying to get my client application to connect and write data to the server. My client application is using QTcpSocket, my client calls connectToHost, the connection is established to the server. I then try to write data to the server module by emitting a signal to write:

    emit write(objJSON);
    

    objJSON contains:

    {"msgType":"ready","source":"Simon"}
    

    The signal is connected to the client socket using:

    QObject::connect(this, &clsMsgSender::write
                    ,this, &clsMsgSender::onWrite, Qt::DirectConnection);
    

    The slot onWrite:

    void clsMsgSender::onWrite(QJsonObject objJSON) {    
        if ( mpsckClient == nullptr ) {
            return;
        }
        QAbstractSocket::SocketState sckState = eGetSockState();
    
        if ( sckState != QAbstractSocket::ConnectedState ) {
    #if defined(DEBUG_SOCKETS)
            qdbg() << "Not connected!";
    #endif
            return;
        }
    #if defined(DEBUG_SOCKETS)
        QString strMsg(QJsonDocument(objJSON).toJson(QJsonDocument::Compact));
        qdbg() << QString("clsMsgSender::onWrite, to: %1:%2, data: %3")
                    .arg(mpsckClient->peerAddress().toString(), QString::number(mpsckClient->peerPort()), strMsg);
    #endif
        clsJSON::addCommonJSONflds(objJSON, nullptr, mpModule);
        //Insert a unique message ID into the message
        objJSON.insert(clsJSON::mscszMsgID, QString::number(clsMsgSender::ulnglngGetUniqueMsgID()));
        //Associate this TCP socket with the output data stream
        QByteArray arybytMsg;
        arybytMsg = QJsonDocument(objJSON).toJson(QJsonDocument::Compact);
        //Write message
        qint64 int64Written = mpsckClient->write(arybytMsg);
    #if defined(DEBUG_SOCKETS)
        qdbg() << QString("clsMsgSender::onWrite, written:%1")
                    .arg(int64Written);
    #endif
        if ( int64Written > 0 ) {
        //Remove the item from the queue
            mqueMsgsOut.dequeue();
        }
        //No longer busy
        mblnBusy = false;
    }
    

    When the write signal is emitted, Qt Creator halts in qiodevice.h at line 137 on:

    { return write(data.constData(), data.size()); }
    

    The function eGetSockState contains:

    QAbstractSocket::SocketState clsMsgSender::eGetSockState() {
        QMutexLocker lock(&mMutex);
        if ( mpsckClient != nullptr ) {
            return mpsckClient->state();
        }
        return QAbstractSocket::UnconnectedState;
    }
    

    There is no breakpoint at on or near this, the slot checks for a valid connection, so what's going on?


  • Lifetime Qt Champion

    @SPlatten said in QTcpSocket client, write problem...:

    When the write signal is emitted, Qt Creator halts in qiodevice.h at line 137 on:

    And how does the stack trace look like? What is the last line of your own code?



  • @jsulm, when Qt Creator stops at line 137, the stack trace:

    1   __pthread_kill                                                                                                                                                                                    (x86_64) /usr/lib/system/libsystem_kernel.dylib                                        0x7fff20317462 
    2   pthread_kill                                                                                                                                                                                      (x86_64) /usr/lib/system/libsystem_pthread.dylib                                       0x7fff20345610 
    3   abort                                                                                                                                                                                             (x86_64) /usr/lib/system/libsystem_c.dylib                                             0x7fff20298720 
    4   qt_message_fatal(QtMsgType, QMessageLogContext const&, QString const&)                                                                                                                            (x86_64) /Users/sy/Qt/5.15.2/clang_64/lib/QtCore.framework/Versions/5/QtCore           0x101534529    
    5   QMessageLogger::warning(const char *, ...) const                                                                                                                                                  (x86_64) /Users/sy/Qt/5.15.2/clang_64/lib/QtCore.framework/Versions/5/QtCore           0x10153505b    
    6   QSocketNotifier::setEnabled(bool)                                                                                                                                                                 (x86_64) /Users/sy/Qt/5.15.2/clang_64/lib/QtCore.framework/Versions/5/QtCore           0x1017506cb    
    7   QAbstractSocket::writeData(const char *, long long)                                                                                                                                               (x86_64) /Users/sy/Qt/5.15.2/clang_64/lib/QtNetwork.framework/Versions/5/QtNetwork     0x101433d4a    
    8   QIODevice::write(const char *, long long)                                                                                                                                                         (x86_64) /Users/sy/Qt/5.15.2/clang_64/lib/QtCore.framework/Versions/5/QtCore           0x1016615fe    
    9   QIODevice::write(QByteArray const&)                                                                                                                                                               qiodevice.h                                                                        137 0x100012341    
    10  clsMsgSender::onWrite(QJsonObject)                                                                                                                                                                clsMsgSender.cpp                                                                   195 0x100026ad8    
    11  QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, QtPrivate::List<QJsonObject>, void, void (clsMsgSender:: *)(QJsonObject)>::call(void (clsMsgSender:: *)(QJsonObject), clsMsgSender *, void * *) qobjectdefs_impl.h                                                                 152 0x10002c3db    
    12  void QtPrivate::FunctionPointer<void (clsMsgSender:: *)(QJsonObject)>::call<QtPrivate::List<QJsonObject>, void>(void (clsMsgSender:: *)(QJsonObject), clsMsgSender *, void * *)                   qobjectdefs_impl.h                                                                 185 0x10002c328    
    13  QtPrivate::QSlotObject<void (clsMsgSender:: *)(QJsonObject), QtPrivate::List<QJsonObject>, void>::impl(int, QtPrivate::QSlotObjectBase *, QObject *, void * *, bool *)                            qobjectdefs_impl.h                                                                 418 0x10002c265    
    14  void doActivate<false>(QObject *, int, void * *)                                                                                                                                                  (x86_64) /Users/sy/Qt/5.15.2/clang_64/lib/QtCore.framework/Versions/5/QtCore           0x101747f95    
    15  clsMsgSender::write(QJsonObject)                                                                                                                                                                  moc_clsMsgSender.cpp                                                               239 0x10003ce22    
    16  clsMsgSender::run()                                                                                                                                                                               clsMsgSender.cpp                                                                   284 0x100028b42    
    17  QtPrivate::FunctorCall<QtPrivate::IndexesList<>, QtPrivate::List<>, void, void (clsMsgSender:: *)()>::call(void (clsMsgSender:: *)(), clsMsgSender *, void * *)                                   qobjectdefs_impl.h                                                                 152 0x10002c6d4    
    18  void QtPrivate::FunctionPointer<void (clsMsgSender:: *)()>::call<QtPrivate::List<>, void>(void (clsMsgSender:: *)(), clsMsgSender *, void * *)                                                    qobjectdefs_impl.h                                                                 185 0x10002c648    
    19  QtPrivate::QSlotObject<void (clsMsgSender:: *)(), QtPrivate::List<>, void>::impl(int, QtPrivate::QSlotObjectBase *, QObject *, void * *, bool *)                                                  qobjectdefs_impl.h                                                                 418 0x10002c585    
    20  QObject::event(QEvent *)                                                                                                                                                                          (x86_64) /Users/sy/Qt/5.15.2/clang_64/lib/QtCore.framework/Versions/5/QtCore           0x10173fb9f    
    ... <More> 
    

    Entry #15 is the emit of the write signal, #10 is the onWrite slot.


  • Lifetime Qt Champion

    @SPlatten There should be an error message? (qt_message_fatal(QtMsgType, QMessageLogContext const&, QString const&))



  • @jsulm I can see that at entry #4, but nothing is displayed, it seems to skip over that. I've just run it again and entries #1 to #8 are grayed, if I click on #8 it jumps into assembler.



  • @SPlatten said in QTcpSocket client, write problem...:

    but nothing is displayed, it seems to skip over that

    ? When debugger breaks, you are just supposed to move up to frame #4 or #5 and look at the value of the string of the message?



  • @JonB As I said in my previous post, those entires are grayed and when I click on those there is no data, no source it stops in assembler.


  • Lifetime Qt Champion

    @SPlatten Start without debugger



  • @jsulm , how is that going to help? I ran it in the debugger because no data is being written, so I'm trying to determine why it isn't writing.



  • @SPlatten

    As I said in my previous post, those entires are grayed and when I click on those there is no data, no source it stops in assembler.

    Ah, I understand.

    So far as I know, all of these messages go via the qInstallMessageHandler(QtMessageHandler handler). In a complex program it is vital you use this to install your own message handler for all messages, including those issued in Qt's own code. Then you can see the message text, or break there in the debugger and look at variables.

    Have you done this for your application?

    [BTW: When you say you see no message now, you are checking in the appropriate tab of the debugger? Anyway, always install your own handler, so you can break etc. there.]



  • @JonB , Thank you, here is my message handler:

    static void qDebugMsgHandler(QtMsgType type, const QMessageLogContext& context, const QString& strMsg) {
        if ( clsDebugService::blnTerminating() == true ) {
        //Application is terminating, do not add anything else to the stack
            return;
        }
    //    static const QString scstrQJsonObject("QJsonObject(");
        static const QChar scqcCarriageReturn('\n')
                          ,scqcCloseBracket(')')
                          ,scqcComma(',')
                          ,scqcOpenBracket('(');
        static QString sstrLastFile, sstrLastFunction;
        static long slngLastLine = -1;
        clsDebugService* pService = clsDebugService::pGetService();
        quint16 uint16DebugLevel = clsDebugService::mscintDefaultLevel
               ,uint16MsgOffset;
        QString strFile, strFunction, strInfo, strPrefix;
        int intLevelDelimiterIdx = -1;
        //Look for debug level delimiter: \x2, NOTE we cannot use indexOf to locate it!
        static const char ccDebugLevelDelimiter = (clsDebugService::msccDebugLevelDelimiter.toLatin1() + '0');
        for( int i=0; i<strMsg.length() - 3; i++ ) {
            if ( strMsg[i + 1].toLatin1() == '\\'
              && strMsg[i + 2].toLatin1() == 'x'
              && strMsg[i + 3].toLatin1() == ccDebugLevelDelimiter ) {
                intLevelDelimiterIdx = ++i;
                break;
            }
        }
        if ( intLevelDelimiterIdx > 0 ) {
        //Yes, its the debug level
            uint16DebugLevel = strMsg.midRef(0, intLevelDelimiterIdx).toUInt();
        //Offset to start of message
            uint16MsgOffset = intLevelDelimiterIdx + 3;
        } else {
            uint16MsgOffset = 0;
        }
        if ( uint16DebugLevel > clsDebugService::mscintDefaultLevel ) {
        //Do nothing debug level is higher than this messages level
            return;
        }
        //Start with time stamp
        QTime tmNow = QTime::currentTime();
        strPrefix = tmNow.toString(Qt::ISODateWithMs);
        strPrefix += " ";
        switch( type ) {
        case QtCriticalMsg:
            strPrefix += "C";
            break;
        case QtDebugMsg:
            strPrefix += "D";
            break;
        case QtFatalMsg:
            strPrefix += "F";
            break;
        case QtInfoMsg:
            strPrefix += "I";
            break;
        case QtWarningMsg:
            strPrefix += "W";
            break;
        }
        if ( strPrefix.isEmpty() != true ) {
            strPrefix += ":";
        }
        //Split message into lines?
        QStringList slstParts = strMsg.mid(uint16MsgOffset).split(scqcCarriageReturn);
        quint16 uint16Parts = slstParts.length();
        long lngLine = 0;
        for( quint16 uint16Part=0; uint16Part<uint16Parts; uint16Part++ ) {
            QString strOutput(strPrefix), strPart;
    
            if ( slstParts.length() > 1 ) {
                strPart = slstParts[uint16Part];
    
                if ( strPart.startsWith(scqcComma) ) {
                    strPart = strPart.remove(scqcComma);
                }
                if ( uint16Part == 0 && strPart.startsWith(scqcOpenBracket) ) {
                    strPart = clsDebugService::msccSpace + strPart.mid(1);
                }
                if ( uint16Part == uint16Parts && strPart.endsWith(scqcCloseBracket) ) {
                    strPart = strPart.remove(scqcCloseBracket);
                }
            } else {
                strPart = slstParts[uint16Part];
            }
            if ( strPart.isEmpty() == true ) {
                continue;
            }
    /*Is this used???        if ( strPart.startsWith(scstrQJsonObject) ) {
        //Message contains a JSON object, extract the details
                int intLength = strPart.length() - (scstrQJsonObject.length() + 1);
                QString strJSON = strPart.mid(scstrQJsonObject.length(), intLength);
                QJsonDocument objDoc = QJsonDocument::fromJson(strJSON.toUtf8());
    
                if ( objDoc.isNull() ) {
                    return;
                }
                QJsonObject objJSON = objDoc.object();
                QString strLine(objJSON.take("line").toString());
                strFile = objJSON.take("file").toString();
                lngLine = strLine.toLong();
                strInfo = objJSON.take("msg").toString();
                type = static_cast<QtMsgType>(objJSON.take("type").toInt());
            } else */{
                uint32_t uint32Line = 0;
    
                if ( pService->blnGetOriginDetails(strFile, uint32Line) == true ) {
                    lngLine = static_cast<long>(uint32Line);
                }
                if ( context.function ) {
                    strFunction = QString(context.function);
                    strFunction = strFunction.mid(0, strFunction.indexOf(clsDebugService::msccBrktOpen));
                }
                strInfo = strPart;
            }
            if ( strInfo.trimmed().isEmpty() == true || strInfo.compare(scqcCloseBracket) == 0 ) {
                continue;
            }
            if ( uint16Part == 0 ) {
                if ( lngLine >= 0 && !(slngLastLine == lngLine
                                  && strFile.compare(sstrLastFile) == 0
                                  && sstrLastFunction.compare(strFunction) == 0) ) {
                    sstrLastFile = strFile;
                    slngLastLine = lngLine;
                    sstrLastFunction = strFunction;
    
                    if ( lngLine > 0 ) {
                        strOutput += QString("L%1").arg(lngLine, clsDebugService::mscintLineNoLength
                                                               , clsDebugService::mscintLineNoBase, QChar('0'));
                    }
                    if ( strFile.length() > 0 ) {
                        strOutput += QString("F%1").arg(strFile);
                    }
                    if ( strFunction.length() > 0 ) {
                        strOutput += "[" + strFunction + "]";
                    }
                }            
                if ( strInfo.isEmpty() != true ) {
                    strOutput += strInfo;
                }
                pService->blnPushDebug(strOutput);
            } else if ( uint16Parts > 1 ) {
                strOutput += strInfo;
                pService->blnPushDebug(strOutput);
            }
        }
    }
    

    I think the issue here is that it crashes before the message queue is processed, I'll try putting breakpoints on the message types when QtCriticalMsg, QtFatalMsg and QtWarningMsg.

    So far this is what I've found:

    QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread
    


  • @SPlatten
    The fact that QMessageLogger::warning is calling abort() does not look right. With your extremely complex-looking code there, Im wonder whether it isn't falling over in your own code! You handler at this level should be much simpler, IMHO! Anyway, put in judiciois breakpoints and see of you can break where you can view the messages from QSocketNotifier::setEnabled().



  • @JonB , I think you posted before I edited, see the end of the last post.



  • @SPlatten said in QTcpSocket client, write problem...:

    QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread

    So that's the original faulting message? Yep, well, you know this is a problem in the code you're writing :)


  • Moderators

    Is this across threads? the forced DirectConnection lets me believe that

    QAbstractStrocket uses internal QTimer objects you're calling onWrite via a forced DirectConnection, that means the write is called from the calling thread and that is != the thread where the socket may live -> QTimers can't be started or stoped from an other thread.

    Possibly the reason for your failed write attempt



  • @J-Hilk , thank you, I've removed the Qt::DirectConnection parameter from the connects and now the warning has gone.


  • Moderators

    @SPlatten I don't know if QJsonObject is registered with the meta system by default, if not, you'll have to do that yourself, or the connect won't work.

    But you should get compile/runtime warnings if thats the case



  • @SPlatten
    A while back in another of your post threads you were using explicit Qt::DirectConnections, and I said then (and I think others did too), why are you writing this, it will just lead to trouble.

    So why do you ever put Qt::DirectConnection into your connect()s, I just don't get it, it keeps leading to this kind of problem?



  • @JonB , they are gone now.



  • @J-Hilk , I do register the type.



  • @SPlatten said in QTcpSocket client, write problem...:

    @JonB , they are gone now.

    :) May I suggest you never put them in? The default for connect() is DirectConnection, and Qt only changes that to Queued when it sees it's going across threads. At which point as I understand it you need it to do that, so why interfere? :)

    Might I also suggest you consider simplifying your qDebugMsgHandler()? That's an awful lot of code to potentially go wrong, when you are down in a low-level message handler! And if it does you will lose the original message, as you have seen. At minimum: factor all your stuff out to a separate function, so that qDebugMsgHandler() is lean & clean. And wrap that with a try handler. If it goes doolally, allow the original qDebugMsgHandler() to not fall over but to issue some message in a simple fashion. That way you have some protection for not fouling up and still getting access to the original fault message. Making sure one's low-level error/message handlers do not themselves introduce errors is an important goal.



  • @JonB , thank you, the debugHandler works fine and quite sure there is nothing wrong with it, I accept your comments and in the final release it will be disabled altogether.


  • Qt Champions 2017

    @SPlatten, fix the reason for the warning, so you don't get the warning. In other words, don't enable/disable sockets from different threads.

    @JonB said in QTcpSocket client, write problem...:

    The fact that QMessageLogger::warning is calling abort() does not look right.

    He's running with fatal warnings (as suggested in earlier threads), so it's exactly right!



  • I think I spoke to soon I still have the problem:

    QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread
    

    I've modified all my QObject::connect's, ensuring that I am not using Qt::DirectConnection. I'm still getting this message. I've been careful that I am not calling write from any thread, I always emit a write signal which connects to a slot called onWrite which actually does the::

    qint64 int64Written = mpsckClient->write(arybytMsg);
    


  • @SPlatten said in QTcpSocket client, write problem...:

    I think I spoke to soon I still have the problem:
    QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread

    As an experimented developer, you should know that multithreading can solve problems but always introduce new problems ;)

    When using QObject based classes, you should always ensure that you are using them in the thread in which they have be created / moved.
    So, are you sure qint64 int64Written = mpsckClient->write(arybytMsg); is done in the right thread (mpsckClient->thread() == QThread::currentThread()).



  • @KroMignon I thought that one of the benefits of using signals and slots is that you can be assured that they are thread safe?



  • @SPlatten said in QTcpSocket client, write problem...:

    I thought that one of the benefits of using signals and slots is that you can be assured that they are thread safe?

    Yes it is, but it depends how you are doing it.

    QObject::connect(srcObject, SIGNAL(), destObject, SLOT()) will ensure SLOT() will run in destObject work thread, when signal is emitted.

    EDIT:
    but
    QObject::connect(srcObject, SIGNAL(), destObject, SLOT(), Qt::DirectConnection) will run SLOT() in thread from which signal has been emitted.



  • @KroMignon Thank you, rapidly learning this.



  • @SPlatten said in QTcpSocket client, write problem...:

    Thank you, rapidly learning this.

    Other things you should take care off when you are doing multithreading, is to set parent for class member to ensure that when you are moving the class instance to another thread, all member will follow.

    To better explain what I mean, suppose following example:

    class TestClass : public QObject
    {
        Q_OBJECT
    public:
        explicit TestClass(const QHostAddress &address, quint16 portNum, QHostAddress QObject * parent=nullptr)
             : QObject(parent), mSocket(new QTcpSocket(this))
       {
       }
    private:
        QTcpSocket* mSocket;
    };
    
    class TestClass2 : public QObject
    {
        Q_OBJECT
    public:
        explicit TestClass2(const QHostAddress &address, quint16 portNum, QHostAddress QObject * parent=nullptr)
             : QObject(parent), mSocket(new QTcpSocket())
       {
       }
    private:
        QTcpSocket* mSocket;
    };
    
    

    And somewhere in code:

    auto sock = new TestClass(QHostAddess::LocalHost, 888);
    auto sock2 = new TestClass2(QHostAddess::LocalHost, 889);
    auto t = new QThread();
    sock->moveToThread(t);
    sock2->moveToThread(t);
    t->start();
    

    now:

    • sock and sock->mSocket are moved to thread t.
    • sock2 is move the thead t but sock2->mSocket still lives in thread in which it have been created


  • I feel like I am going around and round in circles and just not progressing. I have one process which listens for data on port 8123. It also starts another process which connects to the first process on the host IP and port 8123.

    The processes connect without problem, I am trying to write data from the second process to the first. I emit a signal from the socket that is used to connectToHost on the first process, I have a slot that connects to the emitted write signal:

    void clsMsgSender::onWrite(QJsonObject objJSON) {    
        if ( mpsckClient == nullptr ) {
            return;
        }
        QAbstractSocket::SocketState sckState = eGetSockState();
    
        if ( sckState != QAbstractSocket::ConnectedState ) {
    #if defined(DEBUG_SOCKETS)
            qdbg() << "Not connected!";
    #endif
            return;
        }
        QMutexLocker lock(&mMutex);
    #if defined(DEBUG_SOCKETS)
        QString strMsg(QJsonDocument(objJSON).toJson(QJsonDocument::Compact));
        qdbg() << QString("clsMsgSender::onWrite, to: %1:%2, data: %3")
                    .arg(mpsckClient->peerAddress().toString(), QString::number(mpsckClient->peerPort()), strMsg);
    #endif
        clsJSON::addCommonJSONflds(objJSON, nullptr, mpModule);
        //Insert a unique message ID into the message
        objJSON.insert(clsJSON::mscszMsgID, QString::number(clsMsgSender::ulnglngGetUniqueMsgID()));
        //Associate this TCP socket with the output data stream
        QByteArray arybytMsg;
        arybytMsg = QJsonDocument(objJSON).toJson(QJsonDocument::Compact);
        //Write message
        qint64 int64Written = mpsckClient->write(arybytMsg);
    #if defined(DEBUG_SOCKETS)
        qdbg() << QString("clsMsgSender::onWrite, written:%1")
                    .arg(int64Written);
    #endif
        if ( int64Written > 0 ) {
        //Remove the item from the queue
            mqueMsgsOut.dequeue();
        }
        //No longer busy
        mblnBusy = false;
    }
    

    When I emit the write signal I take a JSON packet from a queue, the item is only removed from the queue when the write completes. I can see using the Qt Creator debugger that in the onWrite slot the socket write is successful and returns the number of bytes written. However I see no evidence that the first application is receiving the data. What could be the cause?



  • @SPlatten said in QTcpSocket client, write problem...:

    However I see no evidence that the first application is receiving the data. What could be the cause?

    TCP sockets are stream interfaces, and they use buffers (reception/transmission) to avoid sending too much small packets.
    To force data transfer you could do:

    // write to transmission buffer
    qint64 int64Written = mpsckClient->write(arybytMsg);
    // force transmission
    mpsckClient->flush();
    


  • @KroMignon , I'm wondering if sockets is the best mechanism for the purpose I am trying to use them for.

    The main process will launch X number of processes, each process will provide a specific purpose.

    Messages sent from the main process will issue requests to the other processes and the other processes will send the results of these requests back to the main. I am using JSON for packaging data messages between all applications.

    I'm now looking at QSharedMemory, but I don't know if I can use QSharedMemory for this kind of communication?



  • @SPlatten
    You can use shared memory if you prefer. But I think you'll end up with the same sort of thread issues as you're having with TCP.



  • @SPlatten said in QTcpSocket client, write problem...:

    I'm now looking at QSharedMemory, but I don't know if I can use QSharedMemory for this kind of communication?

    If you don't want to lose all done work, I would suggest you to use QLocalServer and QLocalSocket which are kind of TCP socket but which is a more aggressive transmission rule.
    Data are sent almost directly, on entering event loop.
    And you don't have to handle shared lock for shared memory access.



  • @KroMignon , thank you, I will look into QLocalServer and QLocalSocket, it sounds like a better fit as my intention is that all processes will run on the same system.


  • Lifetime Qt Champion

    Hi,

    As already written in one of your other threads: QTcpSocket shall be created in the thread they are going to be used in. They cannot be moved around. You can pass around the descriptor but not the QTcpSocket itself. See the threaded QTcpServer/QTcpClient examples.



  • @SGaist , I create the sockets in the main thread and then use signals to communicate between threads, where the connections are also created in the main thread.


  • Qt Champions 2017

    @SPlatten said in QTcpSocket client, write problem...:

    @SGaist , I create the sockets in the main thread and then use signals to communicate between threads, where the connections are also created in the main thread.

    The place of making the connection(s) is irrelevant. There are 2 things that matter:

    1. For sockets - the thread where the descriptor was initialized into, or in other words the thread where the socket's been opened in. @SGaist is half correct in saying they can't be moved around, they can, but iff they've not been opened yet. The thread that socket's been opened in then is and must be the thread the QObject lives in and is fixed forever and ever until the end of time.

    2. For any signal slot emission - the thread that the signal was emitted from and the receiving object context's thread. (direct connections are uninteresting here). The slot's going to be executed in the receiver's thread, while depending on the signal emission's thread either the call will be direct, or queued. For the same connection this may vary within the program if the (same) signal emissions happen from different threads (which is possible), so sometimes it may default to direct calls, sometimes to queued calls. The point is, however, that you shouldn't care. The design should be made such that it doesn't matter if the slot's executed immediately or later, which is also why you shouldn't (99% of cases) tamper with the connection type when doing the QObject::connect call.

    I imagine your object threads are wrong and you hold instances of objects that live in a different thread, so that's why you get the errors. To be certain this doesn't happen, always provide a valid parent for QObjects with the major exception of when the object is a root of a QObject tree that's going to be moved to another thread. Thereafter everything is easy peasy.



  • @kshegunov , my listening class implements the incomingConnection method:

    void clsListener::incomingConnection(qintptr sckDescriptor) {    
        //Every new connection will be run in a newly created thread
        clsServer* pThread = new clsServer(sckDescriptor, this);        
        //We have a new connection
        qDebug() << sckDescriptor << " Connecting...";
        //Connect signal/slot
        //Once a thread is not needed, it will be beleted later
        QObject::connect(pThread, &QThread::finished
                        ,pThread, &QThread::deleteLater);
        pThread->start();
    }
    

    From what you are saying the thread created here is wrong in that it passes the socket descriptor?

    The constructor for clsServer:

    clsServer::clsServer(qintptr sckDescriptor, QObject* pParent)
                                                : QThread(pParent)
                                                , msckDescriptor(sckDescriptor)
                                                , mpsckIncoming(nullptr) {
    }
    

    The prototype for clsServer:

     class clsServer : public QThread {
        Q_OBJECT
    
        private:
            qintptr msckDescriptor;
            QTcpSocket* mpsckIncoming;
            mqueJSON mqueMsgsIn;
    
        public:
            explicit clsServer(qintptr sckDescriptor, QObject* pParent = nullptr);
            ~clsServer();
    
            void cleanup();
            void run();
    
        signals:
            void error(QTcpSocket::SocketError socketerror);
    
        public slots:
            void onDataIn();
            void onDisconnected();
            void onErrorOccurred(QAbstractSocket::SocketError error);
        };
    

    The thread body:

    void clsServer::run() {
        QTcpSocket* pSocket = new QTcpSocket();
        //Set the ID
        qdbg() << "clsServer::run";
        if( !pSocket->setSocketDescriptor(msckDescriptor) ) {
        //Something's wrong, we just emit a signal
            emit error(pSocket->error());
            return;
        }
        //Connect socket and signal
        mpsckIncoming = pSocket;
        QObject::connect(mpsckIncoming, &QAbstractSocket::readyRead
                        ,this, &clsServer::onDataIn);
        QObject::connect(mpsckIncoming, &QAbstractSocket::errorOccurred
                        ,this, &clsServer::onErrorOccurred);
        QObject::connect(mpsckIncoming, &QAbstractSocket::disconnected
                        ,this, &clsServer::onDisconnected);
        //We'll have multiple clients, we want to know which is which
        qdbg() << msckDescriptor << " Client connected";
        //Make this thread a loop,
        //thread will stay alive so that signal/slot to function properly
        //not dropped out in the middle when thread dies
        exec();
    }
    

    Actually, the descriptor passed into the constructor is not actually used until the thread body run loop is entered. Again, it looks very wrong now.

    However, the above is the class that listens for incoming data, the class that sends messages from one process to another:

    class clsMsgSender : public QThread {
        Q_OBJECT
    
        private:        
            static const quint16 mscuint16ConnectionTO;
            static mpMsgSenders msmpMsgSenders;
            static clsMsgSender* mspService;
            static qulonglong msulnglngUniqueMsgID;
    
            mutable QMutex mMutex;
            bool mblnBusy, mblnConnected, mblnRun, mblnStopped;
            clsModule* mpModule;
            QTcpSocket* mpsckClient;
            mqueJSON mqueMsgsOut;
            QString mstrHost;
            quint16 muint16Port;
    
            QAbstractSocket::SocketState eGetSockState();
            QJsonObject objAnythingToDo();
            void push(const QJsonObject& crobjJSON);
            void setModule(clsModule* pModule);
            quint16 uint16GetPort();
    
        public:
            explicit clsMsgSender(clsModule* pModule, QObject* pParent = nullptr);
            ~clsMsgSender();
    
            bool blnBusy();
            bool blnRunning() { return mblnRun; }
            mpMsgSenders::iterator itGetMsgSndr(quint16 uint16Port, QString& rstrKey);
            QTcpSocket* pGetClient() { return mpsckClient; }
            static clsMsgSender* pGetMsgSndr(quint16 uint16Port);
            static clsMsgSender* pGetService() { return clsMsgSender::mspService; }
            void setHost(QString strHost);
            static QString strGetLocalIP();
            void terminate() { mblnRun = false; }
            static qulonglong ulnglngGetUniqueMsgID();
    
        signals:
            void connectToHost(const QString& crstrHost, quint16 uint16Port);
            void removeTrkrs(clsMsgSender* pMsgSndr);
            void sendJSON(const QJsonObject& robjJSON);
            void write(QJsonObject robjJSON);
    
        public slots:
            void onConnectToHost(const QString& crstrHost, quint16 uint16Port);
            void onConnected();
            void onDisconnected();
    #if defined(DEBUG_SOCKETS)
            void onErrorOccurred(QAbstractSocket::SocketError sckError);
            void onHostFound();
    #endif
            void onRemoveTrkrs(clsMsgSender* pMsgSndr);
            void onSendJSON(const QJsonObject& crobjJSON);
            void onWrite(QJsonObject robjJSON);
            void run();
        };
    

    Implementation:

    clsMsgSender::clsMsgSender(clsModule* pModule, QObject* pParent)
                        : QThread(pParent)
                        , mblnBusy(false), mblnConnected(false)
                        , mblnRun(true), mblnStopped(false)
                        , mpModule(nullptr)
                        , mpsckClient(new QTcpSocket()) {
        setModule(pModule);
        clsMsgSender::mspService = this;
        QObject::connect(this, &clsMsgSender::connectToHost
                        ,this, &clsMsgSender::onConnectToHost);
        QObject::connect(this, &clsMsgSender::removeTrkrs
                        ,this, &clsMsgSender::onRemoveTrkrs);
        QObject::connect(this, &clsMsgSender::sendJSON
                        ,this, &clsMsgSender::onSendJSON);
        QObject::connect(this, &clsMsgSender::write
                        ,this, &clsMsgSender::onWrite);
        //Thread body
        QObject::connect(this, &QThread::started
                        ,this, &clsMsgSender::run);
        QObject::connect(this, &QThread::finished
                        ,this, &QThread::deleteLater);
        QObject::connect(mpsckClient, &QAbstractSocket::connected
                        ,this, &clsMsgSender::onConnected);
        QObject::connect(mpsckClient, &QAbstractSocket::disconnected
                        ,this, &clsMsgSender::onDisconnected);
    #if defined(DEBUG_SOCKETS)
        QObject::connect(mpsckClient, &QAbstractSocket::errorOccurred
                        ,this, &clsMsgSender::onErrorOccurred);
        QObject::connect(mpsckClient, &QAbstractSocket::hostFound
                        ,this, &clsMsgSender::onHostFound);
    #endif
        mpsckClient->setSocketOption(QAbstractSocket::LowDelayOption, 1);
    }
    


  • @SPlatten As I told you in previous posts, QThread class is a little bit tricky and sub-classing it is in most case not the best way to do.

    Because:

    • the clsServer instance leaves in the thread in which it have been created, supposing main thread
    • mpsckIncoming will leave in the hosted thread

    this means, with:

    QObject::connect(mpsckIncoming, &QAbstractSocket::readyRead, this, &clsServer::onDataIn);
    

    slot onDatin() will run in this thread which is the main thread and using mpsckIncoming will not work properly, because you are in the wrong thread.


Log in to reply