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

Simple program with QTcpServer and multiple QTcpSocket sockets - crash on program exit if sockets on server side are not disconnectFromHost



  • Hi,

    I have simple single-threaded program with QTcpServer that manages multiple clients.

    The idea is simple: when socket sends some ID packet, I bind that socket with that particular ID - ID is just an int - in vector of pairs {int, QTcpSocket* const} on the server side. Then when it's needed the data can be passed from client to client by server. When one client is disconnected then I want to remove that binding from the vector. Which I do on the disconnected() signal with freeing the memory after that socket (using deleteLater()).

    The problem: It crashes whenever the server closes (the program to be specific) while having connected clients. But when clients are first disconnected then everything works fine. The solution is just to disconnect all connected clients firstly in the server-wrapper destructor. When I comment out disconnectSockets(), it's crashing the vector object/memory or it's just the side effect - look at the count value of vector in dtor and when in slot onDisconnected - with info:

    virtual Server::~Server() Elements in vector: 2
    Server::makeConnections()::<lambda()> Disconnected. Elements in vector: 21845
    ASSERT: "asize >= 0 && asize <= aalloc" in file ../../Qt/5.12.5/gcc_64/include/QtCore/qvector.h, line 554
    
    // or
    
    virtual Server::~Server() Elements in vector: 2
    Server::makeConnections()::<lambda()> Disconnected. Elements in vector: 21845
    terminate called after throwing an instance of 'std::bad_alloc'
      what():  std::bad_alloc
    

    Question: What am i missing? Why the vector memory is affected in that case, and why it is mandatory to disconnect all clients first in that case? I don't see any note about it in the documentation. Just to note when no vectors, are used, then everything is ok.

    The program is a simple QtQuick application, in the UI is only one button, which onClicked simply closing program. The code:

    client.h

    #ifndef CLIENT_H
    #define CLIENT_H
    
    #include <QObject>
    #include <QTcpSocket>
    #include <QHostAddress>
    
    class Client : public QObject
    {
        Q_OBJECT
    public:
        Client(int _id) : id(_id) {}
    
        bool setup(const QString& _address, const quint16 _port)
        {
            socket.connectToHost(QHostAddress(_address), _port);
            return socket.waitForConnected(1000);
        }
    
        void send()
        {
            QByteArray _data(QString::number(id).toUtf8());
            socket.write(_data);
        }
    
    private:
        int id;
        QTcpSocket socket;
    };
    
    #endif // CLIENT_H
    

    server.h

    #ifndef SERVER_H
    #define SERVER_H
    
    #include <QObject>
    #include <QTcpSocket>
    #include <QTcpServer>
    #include <QVector>
    #include <QPair>
    #include <QHostAddress>
    
    class Server : public QObject
    {
        Q_OBJECT
    public:
        Server()
        {
            connect(&server, &QTcpServer::newConnection, [this](){ makeConnections(); });
        }
    
        ~Server()
        {
            qInfo() << Q_FUNC_INFO << "Elements in vector:" << bindings.count();
            //disconnectSockets(); // <-- when this line is commented, the program will crash
        }
    
        bool setup(const QString& _address, const quint16 _port)
        {
            return server.listen(QHostAddress(_address), _port);
        }
    
    private:
        QTcpServer server;
        QVector<QTcpSocket*> connectedSockets;
        QVector<QPair<int, QTcpSocket* const>> bindings;
    
        void makeConnections()
        {
            QTcpSocket* const _socket = server.nextPendingConnection();
            connectedSockets.append(_socket);
    
            connect(_socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), [_socket](){
                qCritical() << Q_FUNC_INFO << "Socket error occurred: " << _socket->error();
            });
    
            connect(_socket, &QTcpSocket::disconnected, [_socket, this](){
                qInfo() << Q_FUNC_INFO << "Disconnected" << "Elements in vector:" << bindings.count();
                connectedSockets.removeOne(_socket);
                removeBinding(_socket);
                _socket->deleteLater();
            });
    
            connect(_socket, &QAbstractSocket::readyRead, [_socket, this](){ processData(_socket); });
        }
    
        void processData(QTcpSocket* const _socket)
        {
            const auto _data = _socket->readAll();
            bindings.append({QString(_data[0]).toInt(), _socket});
        }
    
        void removeBinding(QTcpSocket* const _socket)
        {
            for(const auto _binding : bindings)
            {
                if(_binding.second == _socket)
                {
                    qInfo() << Q_FUNC_INFO << (bindings.removeOne({_binding.first, _binding.second}) ? "Removed one" : "");
                }
            }
        }
    
        void disconnectSockets()
        {
            for(const auto _socket : connectedSockets)
            {
                _socket->disconnectFromHost();
            }
        }
    };
    
    #endif // SERVER_H
    

    main.cpp

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QTimer>
    
    #include "client.h"
    #include "server.h"
    
    int main(int argc, char *argv[])
    {
        QGuiApplication app(argc, argv);
    
        Server server;
        server.setup("127.0.0.1", 4444);
    
        Client clientA(0);
        clientA.setup("127.0.0.1", 4444);
        clientA.send();
    
        Client clientB(1);
        clientB.setup("127.0.0.1", 4444);
        clientB.send();
    
        QQmlApplicationEngine engine;
        engine.load("qrc:/main.qml");
    
        return app.exec();
    }
    

    pro

    QT += quick network
    
    CONFIG += c++11
    CONFIG -= gui
    
    DEFINES += QT_DEPRECATED_WARNINGS
    
    SOURCES += \
            main.cpp
    
    RESOURCES += qml.qrc
    
    HEADERS += \
        client.h \
        server.h
    

  • Qt Champions 2019

    Ok, now you removed the line so the second error steps in.
    Since you did not disconnect the signal and using the 3-arg connect() function Qt can not know that the receiver is no longer alive and will access the deleted object.

    See also https://github.com/KDE/clazy/blob/master/docs/checks/README-connect-3arg-lambda.md

    And it's also in the docs: https://doc.qt.io/qt-5/qobject.html#connect-4


  • Qt Champions 2019

    So does your current version crash or not?
    When you take a look at the backtrace you will see that you're modifying the vector while iterating over it I would guess.



  • @Christian-Ehrlicher said:

    So does your current version crash or not?

    I edited just a little previous post, to explicitly point where is the problem (server.h):

    ~Server()
    {
        qInfo() << Q_FUNC_INFO << "Elements in vector:" << bindings.count();
        //disconnectSockets(); // <-- when this line is commented, the program will crash
    }
    

    When uncommented works like a charm, when commented it crashes in:

    connect(_socket, &QTcpSocket::disconnected, [_socket, this](){
        qInfo() << Q_FUNC_INFO << "Disconnected" << "Elements in vector:" << bindings.count(); // here the vector memory is already corrupted
        connectedSockets.removeOne(_socket);
        removeBinding(_socket);
        _socket->deleteLater();
    });
    

  • Qt Champions 2019

    I already told you why it crashes when you don't disconnect the signals before looping over the sockets to close them.


  • Qt Champions 2019

    Ok, now you removed the line so the second error steps in.
    Since you did not disconnect the signal and using the 3-arg connect() function Qt can not know that the receiver is no longer alive and will access the deleted object.

    See also https://github.com/KDE/clazy/blob/master/docs/checks/README-connect-3arg-lambda.md

    And it's also in the docs: https://doc.qt.io/qt-5/qobject.html#connect-4


Log in to reply