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

QUdpSocket and QDataStream



  • Hi :-)

    I have a program with server-client communication via JSON data. I use TCP for this, and encapsulate the JSON data in QDataStreams, according to the (sadly inofficial) simple chat example. If I got it right, this is to ensure that the data is correct and complete, after a QDataStream transaction could be finished successfully.

    Recently, I added a second small server that answers UDP broadcasts. The purpose is that a client can discover a server, it broadcasts a JSON message and the server answers with it's socket address.

    Now, UDP is unreliable and connectionless. I wonder if encapsulating the JSON data in a QDataStream is also meaningful if an UDP socket is used, or if a datagram that is smaller than the maximum packet size of 65,507 bytes will be delivered as-is (or not at all) anyway.

    Thanks for all enlightenment ;-)



  • @l3u_ said in QUdpSocket and QDataStream:

    I wonder if encapsulating the JSON data in a QDataStream is also meaningful if an UDP socket

    It is. But it does nothing to guarantee the integrity of the data. The easiest way to do that is to send after the main message a checksum of the data itself. Using this example as a starting point (which I think is what you refer to in your question), you could change ChatClient::sendMessage to:

    void ChatClient::sendMessage(const QString &text)
    {
        if (text.isEmpty())
            return; // We don't send empty messages
        // create a QDataStream operating on the socket
        QDataStream clientStream(m_clientSocket);
        // set the version so that programs compiled with different versions of Qt can agree on how to serialise
        clientStream.setVersion(QDataStream::Qt_5_7);
        // Create the JSON we want to send
        QJsonObject message;
        message["type"] = QStringLiteral("message");
        message["text"] = text;
    // change from here on
    const QByteArray dataToSend = QJsonDocument(message).toJson(QJsonDocument::Compact);
    clientStream << dataToSend << QCryptographicHash::hash(dataToSend,QCryptographicHash::Md5);
    }
    

    and change ChatClient::onReadyRead to

    void ChatClient::onReadyRead()
    {
        // prepare a container to hold the UTF-8 encoded JSON we receive from the socket
        QByteArray jsonData;
    QByteArray jsonDataCheckSum; //changed here
        // create a QDataStream operating on the socket
        QDataStream socketStream(m_clientSocket);
        // set the version so that programs compiled with different versions of Qt can agree on how to serialise
        socketStream.setVersion(QDataStream::Qt_5_7);
        // start an infinite loop
        for (;;) {
            // we start a transaction so we can revert to the previous state in case we try to read more data than is available on the socket
            socketStream.startTransaction();
            // we try to read the JSON data 
            socketStream >> jsonData >> jsonDataCheckSum;
            if (socketStream.commitTransaction()) {
    //changed from here
    if(QCryptographicHash::hash(jsonData ,QCryptographicHash::Md5)!=jsonDataCheckSum){
    // the data was corrupted!
    continue;
    }
    // to here
                // we successfully read some data
                // we now need to make sure it's in fact a valid JSON
                QJsonParseError parseError;
                // we try to create a json document with the data we received
                const QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
                if (parseError.error == QJsonParseError::NoError) {
                    // if the data was indeed valid JSON
                    if (jsonDoc.isObject()) // and is a JSON object
                        jsonReceived(jsonDoc.object()); // parse the JSON
                }
                // loop and try to read more JSONs if they are available
            } else {
                // the read failed, the socket goes automatically back to the state it was in before the transaction started
                // we just exit the loop and wait for more data to become available
                break;
            }
        }
    }
    


  • @VRonin Thanks for the detailed example! But doesn't the QDataStream itself add some checksum to the data? I thought it would do … because I tested my server/client connection with netem, really f**king up the connection with corrupted packets, packet loss, duplication, delay and do on, and the only thing that happened was that it took longer. But everything still worked. Or was that due to TCP being used?



  • @VRonin I think the code you posted only works for a QTcpSocket. I can't do QDataStream socketStream(m_clientSocket) with a QUdpSocket, I have to read the data using readDatagram()



  • @l3u_ said in QUdpSocket and QDataStream:

    I have to read the data using readDatagram()

    You don't if you use connectToHost but given you are reporting the problem I'll assume you don't want to go down that route.
    What you can do is use while(hasPendingDatagrams()) and readDatagram to store the data on a temporary buffer (a QByteArray) before using datastream on the buffer and use QByteArray::mid to discard bytes that were already processed



  • Oh, I thought I couldn't even connectToHost with an UDP socket … thanks for pointing this out ;-)

    But I think I can't use it anyway, because the infrastructure of what I do is quite simple: The client does an UDP broadcast with "Is there a server out there?". So I can't connectToHost here, because I don't know it yet. If the server receives such a broadcast, it answers with "Here I am, this is my socket address" and sends the data back to the sender's address.

    So, speaking of the answer, I actually could do a connectToHost, but only for this one … and at the moment, both the server and the client are derived from the same abstract UDP communication class, so I would have to write more code just to handle half of the (frugal) communication with a "real" connection …


Log in to reply