Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Multi-client TCP server with a thread for each client and DB connection pool
Forum Update on Tuesday, May 27th 2025

Multi-client TCP server with a thread for each client and DB connection pool

Scheduled Pinned Locked Moved Solved General and Desktop
9 Posts 3 Posters 5.0k Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • D Offline
    D Offline
    don-prog
    wrote on 28 Nov 2017, 09:18 last edited by
    #1

    I'm creating a Qt TCP (with SSL) server to which multiple clients should connect and use the DB, and this is the main task. I thought that I can use this architecture: each client has its own thread, and clients use DB via DB connection pool. Code of current architecture:

    Client connection part

    main.cpp:

    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        MyServer server;
    
        server.listen(QHostAddress::Any, 1234);
    
        return a.exec();
    }
    

    myserver.h:

    class MyServer : public QTcpServer
    {
        Q_OBJECT
    
        explicit MyServer(QObject *parent = 0);
        ~MyServer();
    
    protected:
        void incomingConnection(qintptr socketDescriptor) Q_DECL_OVERRIDE;
    
    signals:
        void stopAll();
    };
    

    myserver.cpp:

    MyServer::MyServer(QObject *parent) : QTcpServer(parent)
    {
        //...
    }
    
    MyServer::~MyServer(){
        emit stopAll();
    }
    
    void MyServer::incomingConnection(qintptr socketDescriptor){
        QThread* clientThread = new QThread;
        MyClient *client = new MyClient(socketDescriptor, this);
    
        client->moveToThread(clientThread);
    
        connect(clientThread, SIGNAL(started()), client, SLOT(process()));
    
        connect(client, SIGNAL(finished()), clientThread, SLOT(quit()));
        connect(this, SIGNAL(stopAll()), client, SLOT(stopFromServer()));
        connect(client, SIGNAL(finished()), client, SLOT(deleteLater()));
        connect(clientThread, SIGNAL(finished()), clientThread, SLOT(deleteLater()));
    
        clientThread->start();
    }
    

    myclient.h:

    class MyClient : public QObject
    {
        Q_OBJECT
    public:
        explicit MyClient(int socketDescriptor, MyServer *server);
        ~MyClient();
    
    private:
        QSslSocket* socket = NULL;
    
    public slots:
        void process();
    }
    

    myclient.cpp:

    void MyClient::process(){
        //typical connection things
        connect(this->socket, SIGNAL(encrypted()), this, SLOT(ready()));
    }
    
    void MyClient::ready(){
        connect(socket,SIGNAL(readyRead()),this, SLOT(newData()));
    }
    

    DB connection pool part

    DAO which will be executed in the client threads:

    .cpp:

    //DAO method
    
    QList<QMap<QString, QString>> sqlResult = DatabaseService::executeQuery(sqlCommand);
    
    //sqlResult processing
    

    DatabaseService:

    .h:

    class DatabaseService
    {
    public:
        //...
        static QMap<QString, QString> executeQuery(QString command);
    
    private:
        static QThreadStorage<QSqlDatabase> mDatabasePool;
        static QSqlDatabase getDatabase();
    
    };
    

    .cpp:

    //...
    QThreadStorage<QSqlDatabase> DatabaseService::mDatabasePool;
    
    QSqlDatabase DatabaseService::getDatabase()
    {
        if(DatabaseService::mDatabasePool.hasLocalData()) {
            return DatabaseService::mDatabasePool.localData();
        } else {
            auto database = QSqlDatabase::addDatabase("QPSQL", QUuid::createUuid().toString());
            database.setHostName("hostName");
            database.setDatabaseName("databaseName");
            database.setUserName("user");
            database.setPassword("password");
            database.open();
            DatabaseService::mDatabasePool.setLocalData(database);
            return database;
        }
    }
    
    QMap<QString,QString> DatabaseService::executeQuery(QString command){
    
        QSqlQuery query (DatabaseService::getDatabase());
        query.exec(command);
    
        //...
    
        return result;
    }
    
    //...
    

    But after this answer on the CodeReview(please check the answer and discussion below) I think maybe I need to use LibEvent library.

    So, I have two questions:

    1. Should I use LibEvent to manage a large number of network connections and how can I do this? Or maybe I can use native Qt solutions for this?
    2. How good is current architecture? What could be fixed and improved and how?

    1 Reply Last reply
    0
    • V Offline
      V Offline
      VRonin
      wrote on 28 Nov 2017, 11:16 last edited by
      #2
      1. the fact that Qt wants a QThread for each socket is a misconception and actually reduces performance compared to spreading the sockets among QThread::idealThreadCount() threads.
      2. an object in the main thread that handles the db is a good idea but instead of a static method I would implement it async. You send a signal to the db manager with the query, it saves the results in a QVector<QVariantMap> (which is more appropriate for generic handling than QList<QMap<QString, QString>>) and sends them in another signal

      What I would do is separate socket communication from processing. If all MyClient does is receive the data and pass the data along you don't even need multi-threading at all on the socket side

      "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
      ~Napoleon Bonaparte

      On a crusade to banish setIndexWidget() from the holy land of Qt

      D 1 Reply Last reply 29 Nov 2017, 02:46
      4
      • V VRonin
        28 Nov 2017, 11:16
        1. the fact that Qt wants a QThread for each socket is a misconception and actually reduces performance compared to spreading the sockets among QThread::idealThreadCount() threads.
        2. an object in the main thread that handles the db is a good idea but instead of a static method I would implement it async. You send a signal to the db manager with the query, it saves the results in a QVector<QVariantMap> (which is more appropriate for generic handling than QList<QMap<QString, QString>>) and sends them in another signal

        What I would do is separate socket communication from processing. If all MyClient does is receive the data and pass the data along you don't even need multi-threading at all on the socket side

        D Offline
        D Offline
        don-prog
        wrote on 29 Nov 2017, 02:46 last edited by
        #3

        @VRonin

        1. Ok, so, if I will use threads number which will be equals to the QThread::idealThreadCount() and will spread MyClients equally between these threads(of course, I will use one main thread for the MyServer), then question about multithreading and managing network connections will be solved?

        2. DB interaction is fine in the current architecture, but I also can use QtConcurrent::run for async, correct?

        3. So, what about LibEvent? I think now I don't need it and I can use current architecture with the improvements above. Is it correct?

        1 Reply Last reply
        0
        • V Offline
          V Offline
          VRonin
          wrote on 29 Nov 2017, 08:07 last edited by
          #4
          1. Yes but you have to do it manually. in reality I would suggest you keep it all in 1 thread unless you have to run intense processing on the data you receive
          2. not really as DBs are not really made for multi-threading and instances being kept around. Just handle it in the main thread.
            From http://doc.qt.io/qt-5/qsqldatabase.html:

          QSqlDatabase is a value class. Changes made to a database connection via one instance of QSqlDatabase will affect other instances of QSqlDatabase that represent the same connection. Use cloneDatabase() to create an independent database connection based on an existing one.
          Warning: It is highly recommended that you do not keep a copy of the QSqlDatabase around as a member of a class, as this will prevent the instance from being correctly cleaned up on shutdown. If you need to access an existing QSqlDatabase, it should be accessed with database(). If you chose to have a QSqlDatabase member variable, this needs to be deleted before the QCoreApplication instance is deleted, otherwise it may lead to undefined behavior.

          1. LibEvent was suggested by somebody who clearly stated he did not know Qt. I would just ignore that advice

          "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
          ~Napoleon Bonaparte

          On a crusade to banish setIndexWidget() from the holy land of Qt

          D 1 Reply Last reply 30 Nov 2017, 02:12
          2
          • V VRonin
            29 Nov 2017, 08:07
            1. Yes but you have to do it manually. in reality I would suggest you keep it all in 1 thread unless you have to run intense processing on the data you receive
            2. not really as DBs are not really made for multi-threading and instances being kept around. Just handle it in the main thread.
              From http://doc.qt.io/qt-5/qsqldatabase.html:

            QSqlDatabase is a value class. Changes made to a database connection via one instance of QSqlDatabase will affect other instances of QSqlDatabase that represent the same connection. Use cloneDatabase() to create an independent database connection based on an existing one.
            Warning: It is highly recommended that you do not keep a copy of the QSqlDatabase around as a member of a class, as this will prevent the instance from being correctly cleaned up on shutdown. If you need to access an existing QSqlDatabase, it should be accessed with database(). If you chose to have a QSqlDatabase member variable, this needs to be deleted before the QCoreApplication instance is deleted, otherwise it may lead to undefined behavior.

            1. LibEvent was suggested by somebody who clearly stated he did not know Qt. I would just ignore that advice
            D Offline
            D Offline
            don-prog
            wrote on 30 Nov 2017, 02:12 last edited by
            #5

            @VRonin

            1. "Yes but you have to do it manually" What do you mean?
            2. In the previous comment you said that I need "to spreading the sockets among QThread::idealThreadCount() threads", but now you say that "I would suggest you keep it all in 1 thread", so, what exactly should I choose for the current architecture?
            3. Ok, can you share a sample with this DB interaction architecture "You send a signal to the db manager with the query, it saves the results in a QVector<QVariantMap> and sends them in another signal"? Because I can't find good sample, but I need to see it in detail.
            4. Is my current DB interaction architecture with database connections pool is fine if I will fix it only with using cloneDatabase() for instantiation and with using QSqlDatabase::database() for accesing DB?

            Sorry for a lot of questions, and thanks!

            jsulmJ 1 Reply Last reply 30 Nov 2017, 05:34
            0
            • D don-prog
              30 Nov 2017, 02:12

              @VRonin

              1. "Yes but you have to do it manually" What do you mean?
              2. In the previous comment you said that I need "to spreading the sockets among QThread::idealThreadCount() threads", but now you say that "I would suggest you keep it all in 1 thread", so, what exactly should I choose for the current architecture?
              3. Ok, can you share a sample with this DB interaction architecture "You send a signal to the db manager with the query, it saves the results in a QVector<QVariantMap> and sends them in another signal"? Because I can't find good sample, but I need to see it in detail.
              4. Is my current DB interaction architecture with database connections pool is fine if I will fix it only with using cloneDatabase() for instantiation and with using QSqlDatabase::database() for accesing DB?

              Sorry for a lot of questions, and thanks!

              jsulmJ Offline
              jsulmJ Offline
              jsulm
              Lifetime Qt Champion
              wrote on 30 Nov 2017, 05:34 last edited by
              #6

              @don-prog
              2. @VRonin is talking about database access which should be kept in one thread not sockets

              https://forum.qt.io/topic/113070/qt-code-of-conduct

              1 Reply Last reply
              1
              • V Offline
                V Offline
                VRonin
                wrote on 30 Nov 2017, 08:45 last edited by VRonin
                #7
                1. That you have to keep a record of how many sockets are on each thread and decide to send incoming connection to the thread that is less burdened
                2. What I'm saying is that you are optimising prematurely. Try using async sockets in a single thread. in 90% of the cases the performance is acceptable and there is no need to spread them across threads at all
                
                struct QueuedQuery
                {
                    quint64 queryID;
                    QString query;
                    QVariantList positionalBind;
                    QVariantMap nameBind;
                };
                class QueryResult
                {
                public:
                    QueryResult() = default;
                    QueryResult(const QueryResult&) = default;
                    QueryResult& operator=(const QueryResult&) = default;
                    QVariant getResult(int record, int field) const { return m_value.value(record).value(field); }
                    QVariant getResult(int record, const QString& field) const { return m_value.value(record).value(m_fields.indexOf(field)); }
                    const QStringList& fields() const { return m_fields; }
                private:
                    QVector<QVector<QVariant> > m_value;
                    QStringList m_fields;
                    friend class dbManager;
                }
                Q_DECLARE_METATYPE(QueryResult) //You probably need to qRegisterMetaType it too
                class dbManager : public QObject
                {
                    Q_OBJECT
                    Q_DISABLE_COPY(dbManager)
                public:
                    dbManager(QObject* parent = Q_NULLPTR) :QObject(parent), m_autoStart(true) {}
                    void enqueueQuery(const QueuedQuery& query) {/*Do some error checking maybe*/m_queryQueue.append(query); if (m_autoStart) processQueue(); }
                    bool autoStart() const { return m_autoStart; }
                    void setAutoStart(bool val) { m_autoStart = val; }
                    Q_SLOT void processQueue()
                    {
                        while (!m_queryQueue.isEmpty()) {
                            const QueuedQuery currQuery = m_queryQueue.dequeue();
                            QSqlQuery queryToRun;
                            queryToRun.prepare(currQuery.query);
                            for (auto i = currQuery.positionalBind.constBegin(); i != currQuery.positionalBind.constEnd(); ++i)
                                queryToRun.addBindValue(*i);
                            for (auto i = currQuery.nameBind.constBegin(); i != currQuery.nameBind.constEnd(); ++i)
                                queryToRun.bindValue(i.key(), i.value());
                            if (!queryToRun.exec()) queryFailed(currQuery.queryID);
                            bool firstRun = true;
                            QueryResult tempRes;
                            while (queryToRun.next()) {
                                const auto tempRecord = queryToRun.record();
                                const int filedCount = tempRecord.count();
                                for (int i = 0; firstRun && i < filedCount; ++i)
                                    tempRes.m_fields.append(tempRecord.field(i));
                                firstRun = false;
                                QVector<QVariant> recordResult;
                                for (int i = 0; i < filedCount; ++i)
                                    recordResult.append(tempRecord.value(i));
                                tempRes.m_value.append(recordResult);
                            }
                            queryRun(currQuery.queryID, tempRes);
                        }
                        
                    }
                private:
                    bool m_autoStart;
                    QQueue<QueuedQuery> m_queryQueue;
                signals:
                    void queryRun(quint64, const QueryResult&);
                    void queryFailed(quint64);
                };
                
                1. Thanks to the wise @kshegunov

                whether an sql driver is reentrant (thus you can use different QSqlDatabase instances from different threads) is implementation specific.
                I believe it's possible with pgsql
                can't say for the others.

                "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
                ~Napoleon Bonaparte

                On a crusade to banish setIndexWidget() from the holy land of Qt

                D 1 Reply Last reply 30 Nov 2017, 12:51
                5
                • V VRonin
                  30 Nov 2017, 08:45
                  1. That you have to keep a record of how many sockets are on each thread and decide to send incoming connection to the thread that is less burdened
                  2. What I'm saying is that you are optimising prematurely. Try using async sockets in a single thread. in 90% of the cases the performance is acceptable and there is no need to spread them across threads at all
                  
                  struct QueuedQuery
                  {
                      quint64 queryID;
                      QString query;
                      QVariantList positionalBind;
                      QVariantMap nameBind;
                  };
                  class QueryResult
                  {
                  public:
                      QueryResult() = default;
                      QueryResult(const QueryResult&) = default;
                      QueryResult& operator=(const QueryResult&) = default;
                      QVariant getResult(int record, int field) const { return m_value.value(record).value(field); }
                      QVariant getResult(int record, const QString& field) const { return m_value.value(record).value(m_fields.indexOf(field)); }
                      const QStringList& fields() const { return m_fields; }
                  private:
                      QVector<QVector<QVariant> > m_value;
                      QStringList m_fields;
                      friend class dbManager;
                  }
                  Q_DECLARE_METATYPE(QueryResult) //You probably need to qRegisterMetaType it too
                  class dbManager : public QObject
                  {
                      Q_OBJECT
                      Q_DISABLE_COPY(dbManager)
                  public:
                      dbManager(QObject* parent = Q_NULLPTR) :QObject(parent), m_autoStart(true) {}
                      void enqueueQuery(const QueuedQuery& query) {/*Do some error checking maybe*/m_queryQueue.append(query); if (m_autoStart) processQueue(); }
                      bool autoStart() const { return m_autoStart; }
                      void setAutoStart(bool val) { m_autoStart = val; }
                      Q_SLOT void processQueue()
                      {
                          while (!m_queryQueue.isEmpty()) {
                              const QueuedQuery currQuery = m_queryQueue.dequeue();
                              QSqlQuery queryToRun;
                              queryToRun.prepare(currQuery.query);
                              for (auto i = currQuery.positionalBind.constBegin(); i != currQuery.positionalBind.constEnd(); ++i)
                                  queryToRun.addBindValue(*i);
                              for (auto i = currQuery.nameBind.constBegin(); i != currQuery.nameBind.constEnd(); ++i)
                                  queryToRun.bindValue(i.key(), i.value());
                              if (!queryToRun.exec()) queryFailed(currQuery.queryID);
                              bool firstRun = true;
                              QueryResult tempRes;
                              while (queryToRun.next()) {
                                  const auto tempRecord = queryToRun.record();
                                  const int filedCount = tempRecord.count();
                                  for (int i = 0; firstRun && i < filedCount; ++i)
                                      tempRes.m_fields.append(tempRecord.field(i));
                                  firstRun = false;
                                  QVector<QVariant> recordResult;
                                  for (int i = 0; i < filedCount; ++i)
                                      recordResult.append(tempRecord.value(i));
                                  tempRes.m_value.append(recordResult);
                              }
                              queryRun(currQuery.queryID, tempRes);
                          }
                          
                      }
                  private:
                      bool m_autoStart;
                      QQueue<QueuedQuery> m_queryQueue;
                  signals:
                      void queryRun(quint64, const QueryResult&);
                      void queryFailed(quint64);
                  };
                  
                  1. Thanks to the wise @kshegunov

                  whether an sql driver is reentrant (thus you can use different QSqlDatabase instances from different threads) is implementation specific.
                  I believe it's possible with pgsql
                  can't say for the others.

                  D Offline
                  D Offline
                  don-prog
                  wrote on 30 Nov 2017, 12:51 last edited by
                  #8

                  @VRonin
                  Ok, almost done. Thanks for the provided sample, but for what now we need this design with query queue if we have dbManager and sockets in the same thread? Can we do this using usual function call?

                  V 1 Reply Last reply 30 Nov 2017, 13:03
                  0
                  • D don-prog
                    30 Nov 2017, 12:51

                    @VRonin
                    Ok, almost done. Thanks for the provided sample, but for what now we need this design with query queue if we have dbManager and sockets in the same thread? Can we do this using usual function call?

                    V Offline
                    V Offline
                    VRonin
                    wrote on 30 Nov 2017, 13:03 last edited by
                    #9

                    @don-prog Absolutely, if you have everything in the same thread feel free to use it in the usual way without complications

                    "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
                    ~Napoleon Bonaparte

                    On a crusade to banish setIndexWidget() from the holy land of Qt

                    1 Reply Last reply
                    3

                    1/9

                    28 Nov 2017, 09:18

                    • Login

                    • Login or register to search.
                    1 out of 9
                    • First post
                      1/9
                      Last post
                    0
                    • Categories
                    • Recent
                    • Tags
                    • Popular
                    • Users
                    • Groups
                    • Search
                    • Get Qt Extensions
                    • Unsolved