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

Listening and receiving data from a socket.



  • I've started over with sockets and trying to build up something that works using the fortuneclient and fortuneserver sample code.

    I have created simple class called clsListener:

            #include <map>
        
            #include <QAbstractSocket>
            #include <QDataStream>
            #include <QTcpServer>
            #include <QTcpSocket>
        
            class clsListener;
            typedef std::map<quint16, clsListener*> mpListeners;
        
            class clsListener : public QObject {
            Q_OBJECT
        
            private:
                static mpListeners msmpListeners;
        
                QDataStream mdsIn;
                QTcpServer* mpServer;
                quint16 muint16Port;
        
            public:
                clsListener(quint16 uint16Port, QObject* pParent = nullptr);
                ~clsListener();
        
                static clsListener* pCreateListener(quint16 uint16Port);
        
            public slots:
                void onAcceptError(QAbstractSocket::SocketError socketError);
                void onDataIn();
                void onErrorOccurred(QAbstractSocket::SocketError socketError);
                void onNewConnection();
            };
    

    The implementation:

    #include <utility>
    
    #include "clsDebugService.h"
    #include "clsListener.h"
    //Static initialisation
    mpListeners clsListener::msmpListeners;
    /**
     * @brief clsListener class constructor
     * @param uint16Port : Port to listen on
     * @param pParent : Pointer to parent
     */
    clsListener::clsListener(quint16 uint16Port, QObject* pParent)
                                                : muint16Port(uint16Port) {
        if ( clsListener::msmpListeners.find(uint16Port)
                != clsListener::msmpListeners.end() ) {
            throw "Port already in use by another listener!";
        }
        clsListener::msmpListeners.insert(std::make_pair(muint16Port, this));
        //Connect server signals
        mpServer = new QTcpServer(pParent);
        QObject::connect(mpServer, &QTcpServer::acceptError
                            ,this, &clsListener::onAcceptError);
        QObject::connect(mpServer, &QTcpServer::newConnection
                            ,this, &clsListener::onNewConnection);
        //Start listening to port
        if ( !mpServer->listen(QHostAddress::Any, muint16Port) ) {
            qdbg() << "Unable to lisen to port " << muint16Port;
        }
        qdbg() << "Listening on port: " << muint16Port;
    }
    /**
     * @brief Removes listener
     */
    clsListener::~clsListener() {
        if ( clsListener::msmpListeners.find(muint16Port)
                != clsListener::msmpListeners.end() ) {
            clsListener::msmpListeners.erase(muint16Port);
        }
        if ( mpServer != nullptr ) {
            mpServer->close();
            delete mpServer;
        }
    }
    /**
     * @brief This signal is emitted when accepting a new connection results in
     * an error. The socketError parameter describes the type of error that
     * occurred.
     * @param socketError
     */
    void clsListener::onAcceptError(QAbstractSocket::SocketError socketError) {
        Q_UNUSED(socketError);
    }
    /**
     * @brief Slot to receive data in from client
     */
    void clsListener::onDataIn() {
        QByteArray arybytMsg;
    
        mdsIn.startTransaction();
        mdsIn >> arybytMsg;
    
        if ( !mdsIn.commitTransaction() ) {
            return;
        }
    }
    /**
     * @brief This signal is emitted after an error occurred. The socketError
     * parameter describes the type of error that occurred.
     * @param socketError is not a registered metatype, so for queued connections,
     * you will have to register it with Q_DECLARE_METATYPE() and
     * qRegisterMetaType().
     */
    void clsListener::onErrorOccurred(QAbstractSocket::SocketError socketError) {
        //Close the connection
        QTcpSocket* pClient = static_cast<QTcpSocket*>(mdsIn.device());
    
        if ( pClient != nullptr ) {
            pClient->close();
        }
        qdbg() << "Socket error(" << socketError
               << ") https://www.google.com/search?rls=en&q=socket+error+code+"
               << socketError << "&ie=UTF-8&oe=UTF-8"
               << socketError << ".php";
    
    }
    /**
     * @brief This signal is emitted every time a new connection is available.
     */
    void clsListener::onNewConnection() {
        QTcpSocket* pClient = mpServer->nextPendingConnection();
    
        if ( pClient->isOpen() != true ) {
            return;
        }
        //Set-up datastream
        mdsIn.setDevice(pClient);
        mdsIn.setVersion(QDataStream::Qt_5_15);
        //Connect signals and slots
        QObject::connect(pClient, &QAbstractSocket::disconnected
                    ,pClient, &QObject::deleteLater);
        QObject::connect(pClient, &QTcpSocket::readyRead
                    ,this, &clsListener::onDataIn);
        QObject::connect(pClient, &QAbstractSocket::errorOccurred
                    ,this, &clsListener::onErrorOccurred);
    }
    /**
     * @brief Creates a new listener
     * @param uint16Port : Port to listen on
     * @return Pointer to the new listener
     */
    clsListener* clsListener::pCreateListener(quint16 uint16Port) {
        return new clsListener(uint16Port);
    }
    

    Its still very much a work in progress, I'm using a web-browser as the client and my code is listening on the local IP address and port 8124. Using Safari I type into the URL:

    http://localhost:8124/?{"hello":"world"}
    

    I can see using the debugger that a new connection is established and that the signal readyRead is emitted and my slot onDataIn is called, but I receive no data. What haven't I done?

    I've single stepped into:

    QDataStream &operator>>
    

    And I can see that the ba (QByteArray&) passed in as the second parameter does get data but then its cleared as the return of 'readRawData' does not match 'blockSize'.

    I've tried using a QString which uses a different version of the same operator but the result is the same.



  • Can anyone shed some light on this, I really can't see what I've done wrong and tracing the source through the QDataStream >> operator it looks like the problem is in the library, which doesn't sound likely, what have I done wrong?


  • Lifetime Qt Champion

    @SPlatten said in Listening and receiving data from a socket.:

    ?{"hello":"world"}

    What should this be? At least no QDataStream data...



  • @SPlatten
    While you're waiting for someone who understands better than I....

    You are reading data into a QByteArray in a transaction. But the sender has not sent a QByteArray (has it?). So what do you expect to happen?

    EDIT Mine crossed with @Christian-Ehrlicher's. Let me try to hint briefly. To use <<, and transactions, the client must be sending QDataStream objects for you to receive them. Your client is not, it's just sending a stream of bytes.

    That means, all you can do is read whatever bytes happen to be there when readyRead is raised. And that could be anything from the whole string down to just 1 byte. As it stands --- without inventing some protocol for properly sending messages --- all you can do is keep accumulating received bytes until the final } arrives. Push bytes received into a member buffer. Exit the readyRead handler and allow it to be called again for further bytes. Append future bytes to the pending buffer. When it contains a } you have received the complete client "message".

    If the client were a Qt program, and it was using QDataStream to send a QByteArray for the message, then you could use socket >> QByteArray and a transaction. When bytes arrived the transaction would look to see whether the whole QByteArray was there. If not all bytes arrived yet, it would buffer those arrived and commitTransaction() returns false. When more bytes arrive, it would try appending them. When the whole array received, commitTransaction() will return true.



  • @Christian-Ehrlicher , this is just test data on the URL to see that something is being received.



  • @JonB , how the data is received is nothing to do with how it is sent, I can receive the same data as either a QString or array, the content is the same. I've also tested the same code using a QString, the result is the same.

    What I mean by this is that receiving data in a QByteArray or QString makes no difference, the content would be the same, the only difference is the way 0 (nulls) are handled.



  • @SPlatten
    You are failing to understand. Nothing to do with QByteArray vs QString.

    how the data is received is nothing to do with how it is sent

    Not at all true, given that you are using QByteArray, and transactions. How it is sent totally does matter. Nothing other than client sending a QByteArray via a QDataStream will satisfy your server receiver code. You are thinking, somehow, that bytes sent without QDataStream can be received as QDataStream, which they can't.

    I have typed in a detailed explanation in an EDIT to my previous to try to help you. Until you understand that you are going to struggle, I urge you to read it carefully.... :)



  • @JonB , It was my understanding which is obviously wrong that I could receive any data on the socket using a QDataStream, in my testing I'm trying to receive data from a browser which is a standard HTTP request.

    I'm now replacing the QDataStream receive data with:

    void clsListener::onDataIn() {
        QByteArray arybytMsg;
        while( mpClient->bytesAvailable() ) {
            QByteArray arybytMsg = mpClient->readAll();
            qdbg() << "HERE";
        }
    }
    

    Just to see if that resolves the issue.

    [Edit] This now works and I am receiving the data.


  • Lifetime Qt Champion

    @SPlatten said in Listening and receiving data from a socket.:

    It was my understanding which is obviously wrong

    It's all in the docs: "A data stream is a binary stream of encoded information..."
    It really helps (as already told you many times) to read the documentation of a class before using it.



  • @Christian-Ehrlicher , thank you, that just takes time which I don't feel I have.


  • Lifetime Qt Champion

    @SPlatten said in Listening and receiving data from a socket.:

    that just takes time which I don't feel I have.

    You would have solved the issue by yourself 17hours ago when you would have read the documentation.



  • @SPlatten
    This is getting better, but you are still assuming that one readyRead call to onDataIn allows you to write a loop on bytesAvailable() and that will accumulate all bytes sent. It won't. It may do because your message data is so small, but not necessarily, and not if the data gets larger. (Try sending a very long [e.g. many K? megabytes?] string!)

    I showed you the only correct algorithm. Save the data received into a persistent (e.g. member variable) buffer. Append to that as more arrives. Check if you have now received the terminating } from your sending "protocol". When you do, you have the complete message. Period.



  • @SPlatten said in Listening and receiving data from a socket.:

    how the data is received is nothing to do with how it is sent, I can receive the same data as either a QString or array, the content is the same. I've also tested the same code using a QString, the result is the same.

    That's not true, you have to read data in the same way you sent them when you are using QDataStream.
    As TCP is a stream socket, you can not be sure all data have been received, so startTransaction() in combination with commitTransaction() will simply reception side. When not all data are received, QDataStream will rollback the readed data and so you can retry reading on next received data chunk.

    But to ensure this works, you have to read what you have transfered, in the same order.


  • Lifetime Qt Champion

    @SPlatten said in Listening and receiving data from a socket.:

    that just takes time which I don't feel I have

    But you have time to ask in a forum and wait for an answer?!



  • @jsulm , whilst I try to resolve it myself, granted I could also be reading the documentation.


Log in to reply