Qt UDP Socket: How to continuously send request and get response

1.7k views Asked by At

I Have a QT UDP Client and Server program

SERVER code

main.cpp

#include <QCoreApplication>
#include "myserver.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    MyServer myserver;

    return a.exec();
}

myserver.cpp

#include "myserver.h"
#include <iostream>

MyServer::MyServer(QObject *parent)
{
    socket = new QUdpSocket(this);
    clientSocket = new QUdpSocket(this);
    socket->bind(QHostAddress::LocalHost,1234);
 
    connect(socket,SIGNAL(readyRead()),this,SLOT(processClientRequest()));
    qDebug()<<"=============================";
    qDebug()<<"     Server Started     ";
    qDebug()<<"=============================";
}

void MyServer::processClientRequest()
{
    qDebug()<<"processClientRequest()";
    QByteArray buffer;
    buffer.resize(socket->pendingDatagramSize());

    QHostAddress sender;
    quint16 senderPort;
    socket->readDatagram(buffer.data(),buffer.size(),&sender,&senderPort);
    qDebug()<<"Message:"<<buffer;
    
    std::string lineString = buffer.toStdString();

    double response = <some double value here>;//This value gets generated on the server using business some logic
    std::cout<<"response:"<<response<<endl;
    sendResponseDatagram(target);

}

void MyServer::sendResponseDatagram(double target)
{
    QString prefix="Response:";
    QString doubleStr = QString::number(target);
    QString word = prefix + doubleStr;
    QByteArray buffer;
    QHostAddress sender;
    buffer=word.toUtf8();
    clientSocket->writeDatagram(buffer.data(), QHostAddress::LocalHost, 5678 );
}

CLIENT code

main.cpp

#include <QCoreApplication>
#include "myclient.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    MyClient client;

    const int len = 50;

    std::string lineArray[len];
    client.readFileIntoLineArray("MyInputFile.csv",lineArray);

    client.sendBulkArrayDataToServer(lineArray,len);
    qDebug()<<"Here";

    return a.exec();
}

myclient.cpp

#include "myclient.h"
#include <string>
#include <iostream>
#include <fstream>

using namespace std;

MyClient::MyClient(QObject *parent)
{
    mySocket = new QUdpSocket(this);
    serverSocket = new QUdpSocket(this);
    mySocket->bind(QHostAddress::LocalHost,5678);
    connect(mySocket,SIGNAL(readyRead()),this,SLOT(readDatagramsReceivedFromServer()));
}


void MyClient::sendBulkArrayDataToServer(std::string lineArray[],int length)
{
    for(int i=0;i<length;i++)
    {
        std::string line = lineArray[i];
        QString word=QString::fromStdString(line);
        QByteArray buffer;
        QHostAddress sender;
        buffer=word.toUtf8();
        serverSocket->writeDatagram(buffer.data(), QHostAddress::LocalHost, 1234 );
        qDebug()<<"Sent to server";//---->***BREAKPOINT#1***
    }
}

void MyClient::readFileIntoLineArray(std::string filepath,std::string lineArray[])
{
    int i=0;
    try
    {
        std::string line;
        ifstream file(filepath);

        if(file.is_open())
        {
            while(getline(file,line))
            {
                lineArray[i]=line;
                i++;

            }
            file.close();
        }
        else std::cout << "not able to open file";
    }
    catch (ifstream::failure e)
    {
        cout << e.what() << endl;
    }

}


void MyClient::readDatagramsReceivedFromServer()
{

    while (mySocket->hasPendingDatagrams()) {
        QByteArray buffer;
        buffer.resize(mySocket->pendingDatagramSize());
        QHostAddress sender;
        quint16 senderPort;
        mySocket->readDatagram(buffer.data(), buffer.size(),&sender, &senderPort);
        qDebug()<<"Received From Server:"<<buffer;//----->***BREAKPOINT#2***
    }
}
Sent to server
Sent to server
Sent to server
Sent to server
Sent to server
Sent to server
Sent to server
Sent to server
Sent to server
Sent to server
Sent to server
Sent to server
Sent to server
Sent to server
Sent to server
Sent to server
Sent to server
Sent to server
Sent to server
Sent to server
Here
Received From Server: "Response:0.5"
Received From Server: "Response:2.7"
Received From Server: "Response:1.6"
Received From Server: "Response:0"
Received From Server: "Response:2.9"
Received From Server: "Response:3"
Received From Server: "Response:7"
Received From Server: "Response:2.6"
Received From Server: "Response:1"
Received From Server: "Response:2.1"
Received From Server: "Response:0"
Received From Server: "Response:1.6"
Received From Server: "Response:5"
Received From Server: "Response:4"
Received From Server: "Response:8"
Received From Server: "Response:9"
Received From Server: "Response:10"
Received From Server: "Response:11"
Received From Server: "Response:21"
Received From Server: "Response:3"

I was expecting the the client console output should instead be like given below

Sent to server
Received From Server: "Response:0.5"
Sent to server
Received From Server: "Response:2.7"
Sent to server
Received From Server: "Response:1.6"
Sent to server
Received From Server: "Response:0"
Sent to server
...
...
..

Also in case I run in debug mode and put breakpoint on BREAKPOINT#1 (please see above) all I get is

Sent to server
Sent to server
Sent to server

and if I put breakpoint on BREAKPOINT#2 not a single "Received From Server: " appears on the console and all the sent appear at once. What all these mean is that all send request happen all at once and then the received occurs at the client end.

My concern is

  1. I should be able to get one response for one request otherwise in case my programs runs continuously for 24 or 48 hours and keeps sending request continuously then client wont get a single response !

  2. Also I need to map each response with corresponding request in real time which wont happen

EDIT Below()

Even if I do not send the messages in a loop and instead send separately the behavior is same. I changed the code little bit below

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    MyClient client;
    
    const int len = 50;
    
    std::string lineArray[len];
    client.readFileIntoLineArray("MyInputFile.csv",lineArray);
    //client.sendBulkArrayDataToServer(lineArray,len);
    client.sendOneSampleDataToServer(lineArray,1);
    client.sendOneSampleDataToServer(lineArray,2);//----->***BREAKPOINT#3***
    client.sendOneSampleDataToServer(lineArray,3);//----->***BREAKPOINT#4***
    qDebug()<<"Here";

    return a.exec();
}


void MyClient::sendOneSampleDataToServer(std::string lineArray[],int index)
{
        std::string line = lineArray[index];
        QString word=QString::fromStdString(line);
        QByteArray buffer;
        QHostAddress sender;
        buffer=word.toUtf8();
        serverSocket->writeDatagram(buffer.data(), QHostAddress::LocalHost, 1234 );
        qDebug()<<"Sent to server";

}

Adding breakpoint at BREAKPOINT#3 and BREAKPOINT#4 does not result in a any "Received From Server: " messages .

1

There are 1 answers

0
Alexandre On BEST ANSWER

The core application is single threaded, and you send all your messages inside the same loop. So even if you may receive responses already in between, the Qt application will only treat them once returning to the event loop (in exec()). So this is expected, and also simple.

When you want to receive messages in between, you can:

  • Send your messages with a QTimer to space them a bit and have the event loop running inbetween, but that's just more complexity for not much.
  • Call a QEventLoop().processEvents(); manually between the messages, but I'd not especially recommend that.
  • Multi-thread your application (QThread, QtConcurrent), but this gets tricky to handle concurrency correctly and is only worth if you have heavy processing.

If you need to map each request to an answer, I think you will need to add an ID (int) somewhere in your messages to identify them. But if you start to have multiple things in a message, I really recommend to serialize and de-serialize your data properly with a QDataStream on both ends, otherwise it quickly gets messy and complex.

A good way is usually to create a class to hold you message and serialize them, for example something like:

class Message
{
public:
    explicit Message(int id, const QString& text);
    QByteArray toBinary();
    bool fromBinary(const QByteArray& binary);

private:
    int id;
    QString text;
}

QByteArray Message::toBinary()
{
    QByteArray binary;
    QDataStream stream(&binary, QIODevice::WriteOnly);
    // here: set endianness, precision & co for the stream
    stream << id;
    stream << text;
    return binary;
}

bool Message::fromBinary(const QByteArray& binary)
{
    QDataStream stream(binary);
    // here: set endianness, precision & co for the stream
    stream >> id;
    stream >> text;
    return (stream.status == QDataStream::Ok) && stream.atEnd();
}