多线程福布斯服务器

Threaded Fortune Server示例展示了如何创建一个简单网络服务的服务器,该服务使用线程处理不同客户端的请求。它旨在与Fortune Client示例一起运行。

本示例的实现与Fortune Server示例类似,但在此我们将实现一个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的构造函数简单地生成祝福语列表。

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对象,将传入的套接字描述符和一个随机祝福语传递给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的构造函数只是简单地存储套接字描述符和祝福语文本,以便它们可以在稍后的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将祝福语编码到QByteArray中。

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

但与上一个示例不同,我们最后调用QTcpSocket::waitForDisconnected,这将阻塞调用线程直到套接字断开连接。因为我们在一个分离的线程中运行,所以GUI将保持响应。

示例项目 @ code.qt.io

另见 财富服务器财富客户端,以及阻塞式财富客户端

© 2024 The Qt Company Ltd. 本文件中包含的文档贡献均为各自所有者的版权。本文档根据自由软件基金会发布的GNU自由文档许可协议版本1.3的条款进行许可。Qt及其相关标志是芬兰以及全球其他国家的The Qt Company Ltd的商标。所有其他商标均为其各自所有者的财产。