QTcpServer sometimes generates two newConnection() signals for the same connection



  • This started with a failing test in "libmediawiki":https://projects.kde.org/projects/extragear/libs/libmediawiki. I though it was a race condition between a KJob and a QTcpServer, and "asked in the KDE forums":https://forum.kde.org/viewtopic.php?f=64&t=110250. There, I was suggested to ask in the KDE development mailing list. There, "it was pointed out":http://lists.kde.org/?l=kde-devel&m=136266672827472&w=2 that there was no race condition, just that I was expecting QTcpServer to behave one way, and it is behaving differently. This last answer gave me a way to handle the issue. Now, I would like to understand it.

    So, we have the following main function:

    @
    // main.cpp
    #include <QtCore/QCoreApplication>
    #include <QtNetwork/QNetworkAccessManager>
    #include <QtNetwork/QNetworkReply>
    #include "fakeserver.h"

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

    QThread *serverThread = new QThread();
    FakeServer *fakeserver = new FakeServer;
    QObject::connect(serverThread, SIGNAL(started()), fakeserver, SLOT(run()));
    QObject::connect(serverThread, SIGNAL(finished()), fakeserver, SLOT(deleteLater()));
    fakeserver->moveToThread(serverThread);
    serverThread->start();
    
    QNetworkAccessManager manager;
    
    for (int i = 0; i < 10000; i++) {
        QUrl url("http://127.0.0.1:12566");
        QNetworkRequest request(url);
        request.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/x-www-form-urlencoded%1").arg(i));
        QNetworkReply *reply = manager.post( request, "aasdf" );
        while (!reply->isFinished())
          qApp->processEvents();
        reply->close();
        reply->deleteLater();
    }
    
    serverThread->quit();
    
    return 0;
    

    }
    @

    The function starts an instance of FakeServer in a separated thread, and performs 10000 request to the URL http://127.0.0.1:12566. The FakeServer manages a QTcpServer that listens on that address. This is the FakeServer implementation:

    @
    // fakeserver.h
    #ifndef SERVER_H
    #define SERVER_H

    #include <QtCore/QMutex>
    #include <QtCore/QString>
    #include <QtCore/QStringList>
    #include <QtCore/QThread>
    #include <QtNetwork/QTcpServer>
    #include <QtNetwork/QTcpSocket>

    class FakeServer : public QObject
    {
    Q_OBJECT

    public:

    FakeServer(QObject* const parent = 0);
    ~FakeServer();
    

    private Q_SLOTS:

    void newConnection();
    void dataAvailable();
    void run();
    

    private:

    void writeServerPart();
    void readClientPart();
    

    private:

    QSet<QString> m_allRequests;
    QTcpServer*                m_tcpServer;
    mutable QMutex             m_mutex;
    QTcpSocket*                m_clientSocket;
    

    };

    #endif // SERVER_H
    @

    @
    // fakeserver.cpp
    #include "fakeserver.h"

    #include <QtCore/QDebug>

    FakeServer::FakeServer(QObject* const parent)
    : QObject( parent )
    {
    m_clientSocket = 0;
    m_tcpServer = 0;
    }

    FakeServer::~FakeServer()
    {
    delete m_clientSocket;
    delete m_tcpServer;
    }

    void FakeServer::newConnection()
    {
    QMutexLocker locker(&m_mutex);
    m_clientSocket = m_tcpServer->nextPendingConnection();

    connect(m_clientSocket, SIGNAL(readyRead()),
            this, SLOT(dataAvailable()));
    

    }

    void FakeServer::dataAvailable()
    {
    QMutexLocker locker(&m_mutex);
    readClientPart();
    writeServerPart();
    }

    void FakeServer::run()
    {
    m_tcpServer = new QTcpServer();

    if ( !m_tcpServer->listen( QHostAddress( QHostAddress::LocalHost ), 12566 ) )
    {
    }
    
    connect(m_tcpServer, SIGNAL(newConnection()),
            this, SLOT(newConnection()));
    

    }

    void FakeServer::writeServerPart()
    {
    QString response = "HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset="utf-8"\r\n\r\n<api><error code="notext" info="" /> </api>";
    m_clientSocket->write( response.toLocal8Bit() );
    m_clientSocket->close();
    }

    void FakeServer::readClientPart()
    {
    QString data;
    while (m_clientSocket->bytesAvailable())
    {
    data += m_clientSocket->readAll();
    }
    if (m_allRequests.contains(data)) {
    qDebug() << "Already got this request!!!!!!!!!!!" << data;
    } else {
    qDebug() << "Add request" << m_clientSocket << data;
    m_allRequests << data;
    }
    }
    @

    If you run this code, you will see that now and then the 62nd line gets called. This is because, now and then, the newConnection() signal is emitted twice for the same connection (as far as I understand).

    Does anyone know why that is happening? And, is it because the code is wrong at some point, or because that is just the way QTcpServer is meant to work?

    Note: The code was written by Albert Astals Cid. See the "KDE development mailing list archive":http://lists.kde.org/?l=kde-devel&m=136266672827472&w=2.



  • Hello, ~Gallaecio.

    Welcome to Qt Developer Network.

    Can you, please, add this line right in FakeServer::newConnection()?

    @
    qDebug() << "New connection signal is emitted";
    @



  • Oh, right. It’s the socket that emits * readyRead()* twice. Still, is it normal that readyRead() is emitted more than once for the same data?



  • [quote author="Gallaecio" date="1362768257"]Oh, right. It’s the socket that emits * readyRead()* twice. Still, is it normal that readyRead() is emitted more than once for the same data?[/quote]

    No, it isn't. It should be emitted only once. There is a problem in your code, just 'cause Qt code is debugged well :-)



  • [quote author="tucnak" date="1362768836"]There is a problem in your code, just 'cause Qt code is debugged well :-)[/quote]

    Sorry, but I don’t know what you mean. I get that you say that the code is not right, which is not unexpected. The problem is that I don’t know what my mistake is. And “just ‘cause Qt code is debugged well :-)” does not mean anything to me. Could you please be more specific?

    In the meantime, I added a qDebug() after the mutex in dataAvailable() and another one after the mutex in newConnection(). Eventually, the result is:

    bq. FakeServer::newConnection()
    FakeServer::dataAvailable()
    Added request
    FakeServer::newConnection()
    FakeServer::dataAvailable()
    Added request
    FakeServer::newConnection()
    FakeServer::dataAvailable()
    Already got this request!!!!!!!!!!!
    FakeServer::newConnection()
    FakeServer::newConnection()
    FakeServer::dataAvailable()
    Added request


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.