线程化 Fortune 服务器

线程化 Fortune 服务器范例展示如何创建服务器,为使用线程处理来自不同客户端的请求的简单网络服务。它旨在与 Fortune 客户端范例一起运行。

此范例的实现类似于 Fortune 服务器 范例,但这里我们将实现子类化的 QTcpServer 在不同线程启动每个连接。

为此,我们需要 2 个类: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 ()。我们还使用它存储随机 Fortune 列表。

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 子类,其工作是把 Fortune 写入连接套接字。类重实现 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 对象在堆栈。值得注意的是,我们在线程内创建此对象,自动把套接字关联到线程的事件循环。这确保 Qt 不会试着从主线程向套接字交付事件,当我们从 FortuneThread::run() 访问它时。

    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 服务器 范例,我们把 Fortune 编码成 QByteArray 使用 QDataStream .

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

不像先前范例,结束是通过调用 QTcpSocket::waitForDisconnected (),阻塞调用线程直到套接字已断开连接。因为在单独的线程中运行,GUI 将保留响应速度。

范例工程 @ code.qt.io

另请参阅 Fortune 服务器 , Fortune 客户端 ,和 阻塞 Fortune 客户端 .