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

QLocalSocket doesn't emit readyRead



  • I'm a beginner to IPC and sockets in general and I'm seeing behaviour that is puzzling to me.
    I have a server and client application. The client maintains one QLocalSocket connected to the server's QLocalServer. After an initial successful handshake message, the client sends messages corresponding to user events to the server, where the server processes them immediately inside its connection's readyRead slot and returns a response.

    The problem is that the client doesn't seem to acknowledge this response some of the time - its own readyRead signal does not always fire when the server responds. This seems to be generally the case every other time. Upon sending the next message, the client may then fire readyRead TWICE, for both the previous and the new message which are both then received in full. I have tried using flush() or waitforbyteswritten() on the server side to make sure the return messages are actually written - this appears to make no difference.

    However, introducing a tiny sleep - as brief as 1 milisecond - on the client immediately after writing the initial message apparently causes the client's readyRead to be emitted much more reliably when the response comes in. This is surely not a good solution, but I cannot understand the mechanics here that would point me to how this should be done. Besides the sleep, the client returns to its event loop immediately after sending the message, doing nothing but awaiting the response - there shouldn't be anything preventing it from emitting the signal. Since I'm pretty sure that the server is in fact writing and flushing each reply on time reliably, I'm not sure what would cause the messages to 'buffer up' in the fashion that I'm seeing. I've played with making various connections into type Qt::QueuedConnection, but this hasn't seemed to help anywhere I've tried it.

    I'm actually using a small wrapper that handles the message protocol, including the size byte etc. This seems to work fine as the messages come through in full. Having debugged it, it's not a case of any signals being suppressed by the wrapper.

    Client:

    //Has a connection from connection_'s readyRead signal to a handling slot.
    
    void ConnectionManager::sendMessage(QVariantMap messageContent)
    {
        //Called by user
        QDataStream writer(&block,QIODevice::WriteOnly);
        writer << messageContent;
        connection_->write(block);
        //Via the wrapper, prefixes the size to the block then calls the connection's write()
        //QThread::msleep(1) - makes all the difference?
    }
    

    Server:

    void ExtensionBridge::onNewConnection(QSharedPointer<IPC::Connection> connection)
    {
        connect(connection.data(),&IPC::Connection::readyRead,this,[](){
            QByteArray data=connection->read();
            //process data, etc etc
            QByteArray replyBlock;
            QDataStream writer(&replyBlock, QIODevice::WriteOnly);
            writer << replyData;
            connection->write(replyBlock)
        }
    //Initial handshake logic also here
    }
    

    Any ideas on what could be going on here? Why would my client's readyRead not fire? Why would a tiny sleep fix it? If anything I would expect a tiny sleep that prevents the program from returning to the event loop before the reply is written to impede the firing of readyRead, not improve it.



  • QLocalSocket is implemented differently depending on the OS so if you want to know the reason why you have to specify that detail.
    As to the behaviour, it's totally fine. readyRead gives you no guarantee of how much data is available in the socket, it just tells you something is there. You can look at the ChatClient::onReadyRead method of this example to see how to correctly read from sockets using QDataStream and a small explanation on why and how it works.


    P.S.

    QDataStream writer(&block,QIODevice::WriteOnly);
        writer << messageContent;
        connection_->write(block);
    

    There's no need to put block in-between. You can use QDataStream directly on the socket:

    QDataStream writer(connection_);
    writer << messageContent;
    

  • Moderators

    hi @Ananym and welcome,

    I think, but keep in mind I'm unfamiliar with QLocalSocket, the issue is indeed connected to the nature of readyRead and processing queues of your cpu.

    In the docu it's stated:

    readyRead() is not emitted recursively; if you reenter the event loop or call waitForReadyRead() inside a slot connected to the readyRead() signal, the signal will not be reemitted (although waitForReadyRead() may still return true).

    if you emit directly a response via a write request, your client may still be inside the readyRead call -> no reemitting of the signal.

    That's an issue one usually does not run into, as Socket connections do have a latency, but with QLocalSocket that's different.



  • QLocalSocket is implemented differently depending on the OS so if you want to know the reason why you have to specify that detail.

    Windows, sorry. Although I certainly will have to test on other platforms once I can prove it working.

    As to the behaviour, it's totally fine. readyRead gives you no guarantee of how much data is available in the socket, it just tells you something is there. You can look at the ChatClient::onReadyRead method of this example to see how to correctly read from sockets using QDataStream and a small explanation on why and how it works.

    Right, but I'm fairly sure there IS data available in the socket, since it was just written without any visible issue, and my application is sitting in the eventloop without ever having readyRead fire. I've been through the example but I'm guessing that it's something to do with the way that I send a response inside the server's readyRead, instants after the client sends the original message, that's tripping things up - I'm just not sure how.

    There's no need to put block in-between. You can use QDataStream directly on the socket

    This comes down to my wrapper that handles adding the size info to the message.

    if you emit directly a response via a write request, your client may still be inside the readyRead call -> no reemitting of the signal.

    I'm sure it's something LIKE this - but my client never enters readyRead, so I don't see how it can be that particular hangup. The message is initially sent due to a user event, and a reply is received if I sleep a millisecond between sending and returning to the event loop, and apparently goes ignored if I don't. I can't really come up with an idea of what could be happening to account for that difference.

    Thanks for the responses guys!



  • What kind of delay are we talking about? seconds?



  • Not a time delay - without the sleep, the client's readyRead doesn't fire at all, until another message/response takes place whereupon the client emits readyRead twice, receiving the original response and then the new one.

    It generally seems to follow this no signal / two consecutive signals / no signal pattern, but can vary depending on how hard the user hammers it. Which, you'd think it would suggest that the server isn't actually writing the data from the buffer to the device every time - but I've tried flush() and waitforbyteswritten() and examined the bytes written in debug, and I'm pretty sure it is. If that WERE the case, I can't see why the tiny sleep would fix it.

    It almost seems like readyRead only fires if the data is available at the instant the eventloop is resumed - if the data is written while the client is waiting in the event loop, nothing happens.



  • Very strange, I'll put together a quick basic example so we can have a proper test



  • Can you try compiling and run this basic example? It works fine for me (Win7)

    Server

    #include <QCoreApplication>
    #include <QLocalSocket>
    #include <QLocalServer>
    #include <QTimer>
    #include <QDataStream>
    #include <QDebug>
    #include <QTime>
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        QLocalServer localServer;
        QObject::connect(&localServer,&QLocalServer::newConnection,[&localServer](){
            QLocalSocket* sock = localServer.nextPendingConnection();
            Q_ASSERT(sock);
            QObject::connect(sock,&QIODevice::readyRead,[sock](){
                QString tempString;
                QDataStream stream(sock);
                for(;;){
                    stream.startTransaction();
                    stream >> tempString;
                    if(!stream.commitTransaction())
                        return;
                    qDebug() << QStringLiteral("Server Received at ")
                                + QTime::currentTime().toString(Qt::ISODateWithMs)
                                + QStringLiteral(": ")
                                + tempString;
                }
            });
            QTimer* senderTimer = new QTimer(sock);
            QObject::connect(senderTimer,&QTimer::timeout,sock,[sock](){
                QDataStream stream(sock);
                stream << QStringLiteral("Server Sent at ")
                        + QTime::currentTime().toString(Qt::ISODateWithMs);
            });
            senderTimer->start(1000);
        });
        Q_ASSUME(localServer.listen(QStringLiteral("TestPipe")));
        qDebug() << QStringLiteral("Server Started");
        return a.exec();
    }
    

    Client

    #include <QCoreApplication>
    #include <QLocalSocket>
    #include <QTimer>
    #include <QDataStream>
    #include <QDebug>
    #include <QTime>
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        QLocalSocket clientSocket;
        QObject::connect(&clientSocket,&QIODevice::readyRead,[&clientSocket](){
            QString tempString;
            QDataStream stream(&clientSocket);
            for(;;){
                stream.startTransaction();
                stream >> tempString;
                if(!stream.commitTransaction())
                    return;
                qDebug() << QStringLiteral("Client Received at ")
                            + QTime::currentTime().toString(Qt::ISODateWithMs)
                            + QStringLiteral(": ")
                            + tempString;
            }
        });
        QObject::connect(&clientSocket,&QLocalSocket::connected,[&clientSocket](){
            QTimer* senderTimer = new QTimer(&clientSocket);
            QObject::connect(senderTimer,&QTimer::timeout,&clientSocket,[&clientSocket](){
                QDataStream stream(&clientSocket);
                stream << QStringLiteral("Client Sent at ")
                        + QTime::currentTime().toString(Qt::ISODateWithMs);
            });
            senderTimer->start(1000);
        });
        clientSocket.connectToServer(QStringLiteral("TestPipe"));
        qDebug() << QStringLiteral("Client Started");
        return a.exec();
    }
    
    

    To run the test open the server first and then the client you can tweak the argument of senderTimer->start(1000); to increase/decrease the interval between calls



  • Welp. That works fine.
    And when I amended it so the server sends its response inside readyRead, that works fine.
    And when I changed the sending/receiving method to match the more explicit serialization my wrapper uses, that - frustratingly - worked fine.

    Hm! I hope to test it with the same wrapper library I had been using but integrating it is going to take me some time to stumble around. The server application I'm adding to already has quite a busy event loop, so it's hard to say what else might play into it. Thanks a lot for your help, having such an elegant minimal solution does make things easier and this goes a long way to exclude possible factors.


Log in to reply