Qt Tcp 多线程

// 实现目标:每个client连接上来,生成一个子线程来处理。

CMAKE:
find_package(Qt5 COMPONENTS Core Widgets Network REQUIRED)
target_link_libraries(demo1 PRIVATE Qt5::Widgets Qt5::Network)

//clientScoketHandle.h 客户端连接处理
class clientSocketHandle : public QObject
{
    Q_OBJECT
public:
    explicit clientSocketHandle(QObject *parent = nullptr);

public slots:
    // 读取客户端数据
    void onReadyRead();
    //断开
    void onDisconnected();

    void setSocket(qintptr socketDescriptor);

signals:
    // 通知主线程客户端断开连接
    void clientDisconnected(qintptr socketDescriptor);
    // 通知线程退出
    void finished();

private:
    QTcpSocket* clientSocket;
    qintptr m_socket_fd;
    QMutex m_mutex; // 保护Socket读写的互斥锁
};

// .cpp
clientSocketHandle::clientSocketHandle(QObject *parent)
    :QObject(parent)
{
        qDebug() << "创建客户端处理器,Socket描述符:" << m_socket_fd
                 << ",当前是主线程,线程ID:" << QThread::currentThreadId();
}

//收
void clientSocketHandle::onReadyRead()
{
    QMutexLocker locker(&m_mutex); // 加锁保证读操作线程安全
    QString msg = clientSocket->readAll();
    qDebug() << "收到客户端" << m_socket_fd << "消息:" << msg;

    // 发,告诉客户端收到了
    // 示例:回复客户端
    QString reply = "服务端已收到:" + msg;
    clientSocket->write(reply.toUtf8() + "\n"); // 加换行符方便客户端解析
    clientSocket->flush();
    qDebug() << "向客户端" << m_socket_fd << "发送:" << msg;
}

void clientSocketHandle::onDisconnected()
{
    qDebug() << "客户端" << m_socket_fd << "断开连接";
    clientSocket->close();
    //通知主线程
    emit clientDisconnected(m_socket_fd);
    //通知线程退出
    emit finished();
}

// QTcpSocket *socket,
void clientSocketHandle::setSocket(qintptr socketDescriptor)
{
    // clientSocket = socket;
    m_socket_fd = socketDescriptor;
    clientSocket= new QTcpSocket();

    bool bindOk = clientSocket->setSocketDescriptor(m_socket_fd);
    if (!bindOk) {
        qCritical() << "绑定Socket描述符失败!错误码:" << clientSocket->error()
                    << ",错误信息:" << clientSocket->errorString()
                    << ",描述符:" << m_socket_fd;
    } else {
        qDebug() << "客户端连接成功,Socket描述符:" << m_socket_fd
                 << ",处理线程为子线程,ID:" << QThread::currentThreadId();

    }

    QString ip = clientSocket->peerAddress().toString();
    quint16 port = clientSocket->peerPort();

    qDebug() << "客户端 ip 地址:" << ip;
    qDebug() << "客户端端口:"<< QString::number(port);

    // ========== 绑定信号槽(子线程内执行) ==========
    // 接收数据信号
    connect(clientSocket, &QTcpSocket::readyRead,
            this, &clientSocketHandle::onReadyRead,
            Qt::DirectConnection); // 子线程内直接触发

    // 客户端断开信号
    connect(clientSocket, &QTcpSocket::disconnected,
            this, &clientSocketHandle::onDisconnected,
            Qt::DirectConnection);

    // Socket 错误信号
//    connect(clientSocket, &QTcpSocket::errorOccurred,
//            this, [this](QTcpSocket::SocketError error) {
//                qCritical() << "Socket 错误(描述符:" << m_socket_fd << "):"
//                            << clientSocket->errorString();
//            }, Qt::DirectConnection);

    // 打印子线程信息和客户端信息(此时一定有效)
    qDebug() << "子线程ID:" << QThread::currentThreadId()
             << "客户端IP:" << clientSocket->peerAddress().toString()
             << "客户端Port:" << clientSocket->peerPort()
             << "描述符:" << m_socket_fd;
}

// tcpServer.h
class TcpServer : public QTcpServer
{
    Q_OBJECT
public:
    explicit TcpServer(quint16 port = 8888, QObject *parent = nullptr);

signals:
    // 信号:向子线程处理器传递已绑定的 QTcpSocket
    void passSocketToHandler(qintptr socketDescriptor);

public slots:
    void onClientDisconnected(qintptr socketDescriptor);

private:
    quint16 m_port;
    QTcpServer *m_tcpServer = nullptr;
    QMap<qintptr, clientSocketHandle*> m_clientHandlers; // 保存所有在线客户端
    QMutex m_clientMutex; // 保护客户端列表的互斥锁

protected:
    // 关键:声明重写 incomingConnection 虚函数
    //重写QTcpServer的虚函数incomingConnection 用来处理连接产生的socket传给子线程
    void incomingConnection(qintptr  socketDescriptor) override;
};

TcpServer::TcpServer(quint16 port, QObject *parent)
    : QTcpServer(parent),m_port(port)
{
    // 关键:加 listen 结果判断
    bool listenOk = this->listen(QHostAddress::Any, m_port);

    if (listenOk) {
        qDebug() << "服务端启动成功,监听端口:" << m_port
                 << ",主线程ID:" << QThread::currentThreadId();
    } else {
        // 打印失败原因,快速定位问题
        qDebug() << "服务端启动失败!端口:" << m_port
                 << ",原因:" << this->errorString();
    }
}

// 清理断开连接的客户端
void TcpServer::onClientDisconnected(qintptr socketDescriptor)
{
    QMutexLocker locker(&m_clientMutex);
    m_clientHandlers.remove(socketDescriptor);
    qDebug() << "清理客户端" << socketDescriptor << ",当前在线数:" << m_clientHandlers.size();
}

void TcpServer::incomingConnection(qintptr  socketDescriptor)
{
    // ========== 关键1:主线程内创建 QTcpSocket 并绑定描述符 ==========
    QTcpSocket* clientSocket = new QTcpSocket(this);
    // 绑定描述符(主线程操作,Windows 下 100% 成功)
    if (!clientSocket->setSocketDescriptor(socketDescriptor)) {
        qCritical() << "主线程绑定描述符失败:" << clientSocket->errorString()
                    << ",描述符:" << socketDescriptor;
        clientSocket->deleteLater();
        return;
    }

    // 主线程中先获取客户端 IP/端口(此时一定有效)
    qDebug() << "新客户端连接:"
             << "IP=" << clientSocket->peerAddress().toString()
             << "Port=" << clientSocket->peerPort()
             << "描述符=" << socketDescriptor;

    // ========== 关键2:创建子线程和处理器 ==========
    clientSocketHandle* handler = new clientSocketHandle();
    QThread* subThread = new QThread(this);

    // ========== 关键3:传递 Socket 给子线程处理器 ==========
    // 1. 解除 Socket 的父对象(否则无法移到子线程)
    clientSocket->setParent(nullptr);

    /*
    // 2. 连接信号:传递 Socket 和描述符给处理器
    connect(this, &TcpServer::passSocketToHandler,
            handler, &clientSocketHandle::setSocket,
            Qt::QueuedConnection); // 队列连接,适配子线程
  
    //**QueuedConnection 信号的执行,必须等待【当前函数完全跑完】+【回到事件循环】才会执行!见例子A代码
    

    // 3. 发送信号(此时 handler 还未移到子线程,信号会排队到子线程执行)
    emit passSocketToHandler(socketDescriptor);
    */

    // ========== 关键4:线程生命周期管理 ==========
    // 处理器移到子线程
    handler->moveToThread(subThread);

    // 2. 和 3. 也许可以改成
    QMetaObject::invokeMethod(
        handler,
        "setSocket",
        Qt::QueuedConnection,
        Q_ARG(qintptr, socketDescriptor)
    );

    // 客户端断开连接,清理资源
    connect(handler, &clientSocketHandle::clientDisconnected,this, &TcpServer::onClientDisconnected);

    // 处理器完成后,退出线程
    connect(handler, &clientSocketHandle::finished, subThread, &QThread::quit);

    // 线程退出时销毁处理器和线程
    connect(subThread, &QThread::finished, handler, &clientSocketHandle::deleteLater);
    connect(subThread, &QThread::finished, subThread, &QThread::deleteLater);

    {
          QMutexLocker locker(&m_clientMutex);
          m_clientHandlers[socketDescriptor] = handler;
    }

    // 线程启动
    subThread->start();
    // 销毁clientSocket
    clientSocket->deleteLater();
}

int main()
{
     // tcp多线程
    // ========== 关键:注册 qintptr 类型 ==========
    qRegisterMetaType<qintptr>("qintptr");
    TcpServer* server = new TcpServer(8888,this);
}

// 例子A
// MIANWINDOW.H
class Test : public QObject
{
    Q_OBJECT
public slots:
    void testSlot() {
        qDebug() << "【槽函数执行】,线程ID:" << QThread::currentThreadId();
    }
};

// 给信号加个发射函数
class Emitter : public QObject
{
    Q_OBJECT
signals:
    void mySignal(); // 自定义信号,无参数
};


int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    qDebug() << "主线程ID:" << QThread::currentThreadId();
    Test testObj;       // 接收者
    Emitter emitter;    // 发送者

    // ✅ 关键:QueuedConnection 队列连接
    QObject::connect(
        &emitter, &Emitter::mySignal,
        &testObj, &Test::testSlot,
        Qt::QueuedConnection
    );

    // ==============================================
    qDebug() << "1. 开始发射信号";
    emit emitter.mySignal();  // 发射队列信号

    // 🔥 重点:槽函数 【绝对不会立刻执行】
    qDebug() << "2. 信号发射完毕 → 槽函数还没跑!";

    qDebug() << "3. 当前函数继续执行,没有被打断";

    // 只有进入事件循环,槽才会执行
    qDebug() << "4. 调用 processEvents() → 槽函数才执行";
    //a.processEvents();
    // ==============================================

    qDebug() << "5. 函数执行完毕";

    return a.exec();
}

输出结果:
主线程ID: 0x5eb4
1. 开始发射信号
2. 信号发射完毕 → 槽函数还没跑!
3. 当前函数继续执行,没有被打断
4. 调用 processEvents() → 槽函数才执行
5. 函数执行完毕
【槽函数执行】,线程ID: 0x5eb4

// 如果没注释a.processEvents();
主线程ID: 0xdd8
1. 开始发射信号
2. 信号发射完毕 → 槽函数还没跑!
3. 当前函数继续执行,没有被打断
4. 调用 processEvents() → 槽函数才执行
【槽函数执行】,线程ID: 0xdd8
5. 函数执行完毕

posted @ 2026-03-30 14:43  一见无始道成空  阅读(3)  评论(0)    收藏  举报