c++ - Qt QTcpSocket() readReady 信号在多线程服务器应用程序中永远不会触发(插槽从未被调用)。 waitForReadyRead() 方法工作正常

标签 c++ multithreading qt signals qtcpsocket

我正在使用 QTcpServer 和 QTcpSocket 编写一个线程化的 TcpServer(每个客户端都在自己的线程中)。客户端应用程序工作正常,每 3 秒发送一次数据,但 readReady() 信号永远不会触发,这意味着我的 receive_data() 函数永远不会被调用。当使用 socket->waitForReadyRead() 并自己调用 receive_data() 时,一切正常。请看一下下面的代码,也许我在使用 Qt 提供的 moveToThread/connect 功能时犯了一些错误。

客户端.h

#ifndef CLIENT_H
#define CLIENT_H

#include <QThread>
#include <QTcpSocket>
#include <QHostAddress>
#include "PacketDefinitions.h"
#include "tcpserver.h"

class Client : public QObject
{
    Q_OBJECT
public:
    explicit Client(int socket,TcpServer *parent,bool auto_disconnect = true);
    ~Client();
    bool isGameServer(){return is_gameserver;}
    GameServerPacket getGameServerData(){return gameserver;}
    void run();
private:
    QTcpSocket* client;
    TcpServer *parent_server;
    int socket;
    GameServerPacket gameserver;
    ClientPacket clientdata;
    bool is_gameserver;
    bool auto_disconnect;
    QHostAddress client_ip;
    quint16 client_port;
signals:
    void disconnected(Client *);
private slots:
    void remove_from_clientlist();
    void receive_data();
    void display_error(QAbstractSocket::SocketError error);
};

#endif // CLIENT_H

客户端.cpp

#include "client.h"
#include "PacketDefinitions.h"
#include "time.h"
#include <iostream>

Client::Client(int _socket, TcpServer *parent,bool _auto_disconnect)
{
    auto_disconnect = _auto_disconnect;
    parent_server = parent;
    is_gameserver = false;
    socket = _socket;
}

void Client::run(){ 
    client = new QTcpSocket();

    if(client->setSocketDescriptor(socket) == false){
        std::cout << client->errorString().toStdString() << std::endl;
        remove_from_clientlist();
        return;
    }

    connect(client,SIGNAL(disconnected()),this,SLOT(remove_from_clientlist()));
    if(connect(client,SIGNAL(readyRead()),this,SLOT(receive_data()),Qt::DirectConnection) == false) return;
    connect(client,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(display_error(QAbstractSocket::SocketError)));

    client_ip = client->peerAddress();
    client_port = client->peerPort();

    std::cout << "New incomming connection " << client->peerAddress().toString().toStdString() << ":" << client->peerPort() << std::endl;

    //this works fine
//    while(client->waitForReadyRead()){
//        receive_data();
//    }
}

void Client::receive_data(){
       QDataStream stream(client);
       stream.setVersion(QDataStream::Qt_5_2);
       quint32 magic; stream >> magic;
       //interpret data
       if(magic == GAMESERVER_MAGIC){
           is_gameserver = true;
           gameserver.Read(stream);
           gameserver.port = client_port;
           gameserver.ip = client_ip;
           time(&(gameserver.last_update));
           parent_server->add_server(gameserver.ip.toString(),gameserver);
           std::cout << "GameServer " << gameserver.name << " registerd" << std::endl;
       }else if(magic == CLIENT_MAGIC){
           is_gameserver = false;
           clientdata.Read(stream);
           //get nearby servers
           GameServerListPacket server_list = parent_server->getServerList(clientdata);
           QDataStream outstream(client);
           server_list.Write(outstream);
           std::cout << "Sending ServerList(" << server_list.server_count << ") to " << client->peerAddress().toString().toStdString() << std::endl;
           if(auto_disconnect){
               //client->flush();
               client->waitForBytesWritten();
           }

       }else{
           std::cout << "Unknown package " << magic << std::endl;
       }

       //not enough data read, somthing is wrong, just for debugging
       if(client->bytesAvailable()> 0)  std::cout << "BytesAvailable " << client->bytesAvailable() << std::endl;

    if(auto_disconnect) remove_from_clientlist();//close the connection once the serverlist was deployed
}

在 TcpServer.cpp 中,当 QTcpServer 发出 newConnection() 时,会调用 add_client() :

void TcpServer::add_client(){
    while(server->hasPendingConnections()){
        QTcpSocket *socket = 0;
        if(thread_pool.size() < max_connections && (socket = server->nextPendingConnection())){
            QThread *thread = new QThread();
            Client * client = new Client(socket->socketDescriptor(),this,auto_disconnect);
            client->moveToThread(thread);
            client->run();
            thread->start();
            connect(client,SIGNAL(disconnected(Client*)),this,SLOT(remove_client(Client*)));
            WRITELOCK(thread_pool.insert(client,thread));
        }
    }
}

调用 client->run() 和 thread->start() 的顺序似乎并不重要。不久前,代码(不是这个确切的代码)工作正常,但我不记得我更改了什么导致它失败。如有任何帮助,我们将不胜感激!

提前致谢 费边

编辑1:

我从 QTcpServer 派生并重新实现了 voidcomingConnection(qintptr socketDescriptor) ,它工作得很好。我不使用 QThreadPool,它只是一个 QMap,remove_client(Client*) 关闭 QTcpSocket 并停止线程并将其从映射中删除。在 Linux 上一切正常,在 Windows 上我收到以下错误: QSocketNotifier:无法从另一个线程禁用套接字通知程序 QCoreApplication::sendEvent 中的 ASSERT 失败:“无法将事件发送到不同线程拥有的对象....

Screenshot

由这个remove_client(Client*)引起的

void TcpServer::remove_client(Client *client){
    //disconnect(client,SIGNAL(disconnected(Client*)),this,SLOT(remove_client(Client*)));
    lock.lockForWrite();    
    QMap<Client*,QThread*>::iterator itr = thread_pool.find(client);
    if(itr != thread_pool.end()){
        //delete itr.key(); causes the problem on windows
        itr.value()->quit();
        itr.value()->wait();
        delete itr.value();
        thread_pool.erase(itr);
    }
    lock.unlock();
}

我应该在哪里以及如何释放 Client 对象?如果我使用 QThreadPool,如果我想向多个客户端发送消息,则无法迭代客户端。我可以使用仅包含 Client* 的列表/映射,但 QThreadPool 可能会在我想要访问它之前为我删除它们。有什么建议吗?

最佳答案

将客户端对象移动到新线程的方式存在问题。实际上,Client::runTcpServer::add_client 在同一线程中执行。 此外,QTcpSocket 客户端仍保留在默认线程中,而其容器(Client 类)则移至新线程。这就是为什么与 Qt::DirectConnection 类型的连接不起作用的原因。 试试这个:

class Client : public QObject
{
    Q_OBJECT
    ...
public slots:
    void run();
    ...
}

Client::Client(int _socket, TcpServer *parent,bool _auto_disconnect)
{
    ...
    client = new QTcpSocket(this);
}

void Client::run()
{ 
   ...
   connect(client, SIGNAL(readyRead()), this, SLOT(receive_data()));
   ...
}

以下是您应该如何将客户端移至新线程:

void TcpServer::add_client()
{
    ...
    QThread *thread = new QThread();
    Client * client = new Client(socket->socketDescriptor(),this,auto_disconnect);
    client->moveToThread(thread);
    connect(thread, SIGNAL(started()), client, SLOT(run()));
    thread->start();
    ...
}

关于c++ - Qt QTcpSocket() readReady 信号在多线程服务器应用程序中永远不会触发(插槽从未被调用)。 waitForReadyRead() 方法工作正常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23876634/

相关文章:

c++ - Qt:QGraphicsLineItem在QGraphicsScene中的位置

c++ - 在 std::conditional 中使用不完整类型的 sizeof

c# - 任务继续阻塞 UI 线程

android - 使用 Otto 从 GcmListenerService 刷新列表适配器

c++ - 在 C++ 中将值从字符串输入到 wchar 变量

c++ - 从整数到枚举 QEvent::Type 的首选转换样式

c++ - 提供第二个可选参数而不需要提供第一个可选参数?

c++ - Premake 生成的解决方案不会在 Release 中编译,但会在 Debug 中编译

c++ - 有没有更好的方法来初始化 C++ 中分配的数组?

Java volatile 关键字 - 我需要它吗?