多线程 Fortune 服务器

“多线程 Fortune 服务器”示例演示了如何创建一个简单的网络服务服务器,该服务器使用线程来处理来自不同客户端的请求。该示例旨在与“Fortune 客户端”示例配合运行。

该应用程序会创建一个服务器,该服务器能够为每个客户端分配一个单独的线程

本示例的实现与“Fortune 服务器”示例类似,但在此我们将实现QTcpServer 的一个子类,该子类会将每个连接在不同的线程中启动。

为此,我们需要两个类:FortuneServer(QTcpServer 的子类)和 FortuneThread(继承自QThread )。

class FortuneServer : public QTcpServer
{
    Q_OBJECT

public:
    FortuneServer(QObject *parent = nullptr);

protected:
    void incomingConnection(qintptr socketDescriptor) override;

private:
    QStringList fortunes;
};

FortuneServer 继承自QTcpServer ,并重新实现了QTcpServer::incomingConnection() 方法。我们还利用它来存储随机运势的列表。

FortuneServer::FortuneServer(QObject *parent)
    : QTcpServer(parent)
{
    fortunes << tr("You've been leading a dog's life. Stay off the furniture.")
             << tr("You've got to think about tomorrow.")
             << tr("You will be surprised by a loud noise.")
             << tr("You will feel hungry again in another hour.")
             << tr("You might have mail.")
             << tr("You cannot kill time without injuring eternity.")
             << tr("Computers are not intelligent. They only think they are.");
}

我们使用 FortuneServer 的构造函数来生成 fortune 列表。

void FortuneServer::incomingConnection(qintptr socketDescriptor)
{
    QString fortune = fortunes.at(QRandomGenerator::global()->bounded(fortunes.size()));
    FortuneThread *thread = new FortuneThread(socketDescriptor, fortune, this);
    connect(thread, &FortuneThread::finished, thread, &FortuneThread::deleteLater);
    thread->start();
}

我们在QTcpServer::incomingConnection()中的实现会创建一个FortuneThread对象,并将传入的套接字描述符和一条随机 fortune 传递给FortuneThread的构造函数。通过将FortuneThread的finished()信号与QObject::deleteLater()关联,我们确保线程在执行完毕后会被销毁。随后,我们可以调用QThread::start()来启动该线程。

class FortuneThread : public QThread
{
    Q_OBJECT

public:
    FortuneThread(qintptr socketDescriptor, const QString &fortune, QObject *parent);

    void run() override;

signals:
    void error(QTcpSocket::SocketError socketError);

private:
    qintptr socketDescriptor;
    QString text;
};

接下来是 FortuneThread 类,这是一个QThread 的子类,其作用是将占卜语写入已连接的套接字。该类重写了QThread::run() 方法,并提供了一个用于报告错误的信号。

FortuneThread::FortuneThread(qintptr socketDescriptor, const QString &fortune, QObject *parent)
    : QThread(parent), socketDescriptor(socketDescriptor), text(fortune)
{
}

FortuneThread 的构造函数仅用于存储套接字描述符和 fortune 文本,以便稍后在 run() 中使用。

void FortuneThread::run()
{
    QTcpSocket tcpSocket;

我们的 run() 函数首先会在栈上创建一个QTcpSocket 对象。 值得注意的是,我们是在线程内部创建该对象的,这会自动将套接字与该线程的事件循环关联起来。这确保了当我们通过 FortuneThread::run() 访问套接字时,Qt 不会尝试从主线程向我们的套接字发送事件。

    if (!tcpSocket.setSocketDescriptor(socketDescriptor)) {
        emit error(tcpSocket.error());
        return;
    }

通过调用 `QTcpSocket::setSocketDescriptor()` 并传入我们的套接字描述符作为参数,来初始化该套接字。我们预计此操作会成功,但为了保险起见(尽管可能性很小,系统仍可能耗尽资源),我们会捕获返回值并报告任何错误。

    QByteArray block;
    QDataStream out(&block, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_6_5);
    out << text;

与“Fortune Server”示例类似,我们使用 `QDataStream` 将 fortune 编码为 `QByteArray `。

    tcpSocket.write(block);
    tcpSocket.disconnectFromHost();
    tcpSocket.waitForDisconnected();
}

但与前一个示例不同,我们最后会调用 `QTcpSocket::waitForDisconnected()`,该函数会阻塞调用线程,直到套接字断开连接为止。由于我们是在单独的线程中运行的,因此图形用户界面(GUI)将保持响应。

示例项目 @ code.qt.io

另请参阅 “Fortune 服务器”、“Fortune 客户端”和“阻塞式 Fortune 客户端”。

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.