0
votes

I'm attempting to write a tcp server using Qt 5.2 in a scalable way by using QRunnable and QThreadPool instead of 1 thread per connection.

The problem I'm encountering is that the socket created in the QRunnable::run has a status of connected when I create it and set it's socket descriptor , but calling readAll() in response to the readyRead signal yields an empty buffer, even though I know I sent some data.

The two ways I've tried to pass the socket from the main thread to the QRunnable are:

1)Get the QTcpSocket from QTcpServer::nextPendingConnection and pass the socket descriptor to the QRunnable.

2)Override the QTcpServer::incomingConnection and get the socket descriptor from there and pass it to the QRunnable.

Both give the same result, the sockets readyread signal is received but readAll returns an empty buffer.

Any help would be very much appreciated.

1
The QTcpSocket needs to be a child of the new thread so it must be created in the QRunnable::run() function. You then give it the socket descriptor to set it up properly. You must also either have a local event loop to process TCP events, or use the blocking API to wait for data to arrive.RobbieE
@RobbieE , I've already done everything you've mentioned except adding a local event loop. I'm using the non blocking API with signals and slots. I'm not sure where I would need the local event loop if I'm responding to readyRead in the main thread and then kicking off the qrunnable to do a readAll?Rich
You don't kick off the QRunnable after getting the signal. You kick off the QRunnable, create a QTcpSocket inside the run() and then start a local QEventLoop.RobbieE
Ok, I see, I'm starting to realise how the QRunnables work with the threadpool and tcp connection. The QRunnable stays alive for the duration of the connection it's servicing. My misunderstanding was that it stays alive for the duration of the data being sent/received in one direction. Correct me if I'm wrong but otherwise thanks for the clarification.Rich
What is inside your run() function in your QRunnable?RobbieE

1 Answers

2
votes

Thanks to RobbieE for correcting my misunderstanding. Here is a working example of how a Tcp Server using QRunnables and a QThreadpool can be written. I'm new to Qt so any criticisms/improvements/discussion welcomed. The code isn't intended to be production quality, only a simple example of a way you can go about writing a scalable tcp server.

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

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Server server;
    if(server.Start())
    {
        qDebug() << "Server started"   ;
    }
    else
    {
        qDebug() << "Server Failed to start"   ;
    }

    return a.exec();
}

server.h

#ifndef SERVER_H
#define SERVER_H

#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>


class Server : public QObject
{
    Q_OBJECT
public:
    explicit Server(QObject *parent = 0);

    bool Start();

signals:

public slots:
    void onNewConnection();


private:
    QTcpServer* m_pServer;

};

#endif // SERVER_H

server.cpp

#include "server.h"
#include "myrunnable.h"
#include <QThreadPool>

Server::Server(QObject *parent) :
    QObject(parent)
{
    m_pServer = new QTcpServer(this);
}


bool Server::Start()
{
      bool bOK = true;
      if(m_pServer->listen(QHostAddress::Any,1971))
      {
          connect(m_pServer,SIGNAL(newConnection()),this,SLOT(onNewConnection()));
      }
      else
      {
          qDebug() << "Failed to start listening";
          bOK = false;
      }
      return bOK;
}

void Server::onNewConnection()
{
    qDebug() << "onNewConnection";
    QTcpSocket* pSocket = m_pServer->nextPendingConnection();
    qintptr descriptor =  pSocket->socketDescriptor();
    MyRunnable* pRunnable = new MyRunnable();
    pRunnable->setAutoDelete(true);
    pRunnable->setDescriptor(descriptor);
    QThreadPool::globalInstance()->start(pRunnable);

}

myrunnable.h

#ifndef MYRUNNABLE_H
#define MYRUNNABLE_H

#include <QObject>
#include <QTcpSocket>
#include <QRunnable>
#include <QEventLoop>

class MyRunnable : public QObject, public QRunnable
{
    Q_OBJECT
public:
    explicit MyRunnable(QObject *parent = 0);
    ~MyRunnable();
    void run();
    void setDescriptor(qintptr descriptor);

signals:

public slots:
    void onConnected();
    void onDisconnect();
    void onReadyRead();



private:
    qintptr m_socketDecriptor;
    QTcpSocket* m_pSocket;
    QEventLoop* m_pEventLoop;
};

#endif // MYRUNNABLE_H

myrunnable.cpp

#include "myrunnable.h"
#include <QEventLoop>
#include <QThread>

MyRunnable::MyRunnable(QObject *parent) :
    QObject(parent),m_pSocket(0)
{

}

MyRunnable::~MyRunnable()
{
    qDebug() << "MyRunnable destructor called";
}

void MyRunnable::run()
{
    m_pEventLoop = new QEventLoop();
    m_pSocket = new QTcpSocket();
    if(m_pSocket->setSocketDescriptor(m_socketDecriptor))
    {
        connect(m_pSocket,SIGNAL(connected()),this,SLOT(onConnected()),Qt::QueuedConnection);
        connect(m_pSocket,SIGNAL(disconnected()),this,SLOT(onDisconnect()),Qt::QueuedConnection);
        connect(m_pSocket,SIGNAL(readyRead()),this,SLOT(onReadyRead()),Qt::QueuedConnection);
    }

    m_pEventLoop->exec();
    delete m_pSocket;
    delete m_pEventLoop;
}

void MyRunnable::setDescriptor(qintptr descriptor)
{
    m_socketDecriptor = descriptor;
}

void MyRunnable::onConnected()
{
    qDebug() << "Connected";
}

void MyRunnable::onDisconnect()
{
    qDebug() << "Disconnected";
    //m_pEventLoop->disconnect();
    m_pEventLoop->exit();

}

void MyRunnable::onReadyRead()
{
    qDebug() << m_pSocket->readAll();
    for(int i=0;i<4;i++)
    {
        qDebug() << "Sleeping for 5 seconds to simulate work being done.\r\n";
        QThread::sleep(5);
        qDebug() << "...\r\n";
    }

    //qDebug() << "OnReadReady";

}