阻止Fortune客户端
演示如何创建网络服务的客户端。
QTcpSocket支持两种网络编程的通用方法:
class FortuneThread : public QThread { Q_OBJECT public: FortuneThread(QObject *parent = nullptr); ~FortuneThread(); void requestNewFortune(const QString &hostName, quint16 port); void run() override; signals: void newFortune(const QString &fortune); void error(int socketError, const QString &message); private: QString hostName; quint16 port; QMutex mutex; QWaitCondition cond; bool quit; };
void FortuneThread::requestNewFortune(const QString &hostName, quint16 port) { QMutexLocker locker(&mutex); this->hostName = hostName; this->port = port; if (!isRunning()) start(); else cond.wakeOne(); }
void FortuneThread::run() { mutex.lock(); QString serverName = hostName; quint16 serverPort = port; mutex.unlock();
在run()函数中,我们首先获取互斥锁,从成员数据中获取主机名和端口,然后再释放锁。我们正在保护自己免受以下情况的侵害:即在获取此数据的同时调用requestNewFortune()。QtString [QString] 是可以重入的,但 不是 具有线程安全性的,我们还必须避免从某个请求中读取主机名,而从另一个请求中读取端口的低概率风险。如你所猜,FortuneThread每次只能处理一个请求。
run()函数现在进入循环
while (!quit) { const int Timeout = 5 * 1000; QTcpSocket socket; socket.connectToHost(serverName, serverPort);
循环将继续请求致富语言的真言,直到quit为false为止。我们首先在栈上创建一个QTcpSocket,然后调用connectToHost()。这启动了一个异步操作,在控制权返回Qt的事件循环后,将导致QTcpSocket发出connected()或error()。
if (!socket.waitForConnected(Timeout)) { emit error(socket.error(), socket.errorString()); return; }
但由于我们运行在一个非GUI线程中,所以不必担心阻塞用户界面。所以我们不是进入事件循环,而是简单地调用QTcpSocket::waitForConnected()。这个函数将等待,阻塞调用线程,直到QTcpSocket发出connected()或发生错误。如果发出connected(),函数返回true;如果连接失败或超时(在这个例子中是在5秒后),则返回false。QTcpSocket::waitForConnected(),像其他waitFor...()函数一样,是QTcpSocket的阻塞API的一部分。
在这条语句之后,我们有一个可以工作的连接套接字。
QDataStream in(&socket); in.setVersion(QDataStream::Qt_6_5); QString fortune;
现在我们可以创建一个QDataStream对象,并将套接字传递给QDataStream的构造函数,并且与之前的客户端示例一样,我们将流协议版本设置为QDataStream::Qt_6_5。
do { if (!socket.waitForReadyRead(Timeout)) { emit error(socket.error(), socket.errorString()); return; } in.startTransaction(); in >> fortune; } while (!in.commitTransaction());
我们接着通过调用QTcpSocket::waitForReadyRead()来初始化一个循环,等待接收致富语言的真言字符串数据。如果它返回false,我们将终止操作。在这条语句之后,我们开始一个流读事务。当QDataStream::commitTransaction()返回true时,我们退出循环,这意味着成功加载了致富语言的真言。通过发出newFortune()将结果真言交付。
mutex.lock(); emit newFortune(fortune); cond.wait(&mutex); serverName = hostName; serverPort = port; mutex.unlock(); }
循环的最后一部分是我们获取互斥锁,以便我们可以安全地读取我们的成员数据。然后我们通过调用QWaitCondition::wait()让线程休眠。在这个时候,我们可以回到requestNewFortune()并仔细看一下对wakeOne()的调用
void FortuneThread::requestNewFortune(const QString &hostName, quint16 port) { ... if (!isRunning()) start(); else cond.wakeOne(); }
这里发生的事情是,因为线程因等待新的请求而进入睡眠状态,所以在新请求到来时,我们需要再次唤醒它。QWaitCondition常用于线程中,用于信号这种唤醒调用。
FortuneThread::~FortuneThread() { mutex.lock(); quit = true; cond.wakeOne(); mutex.unlock(); wait(); }
完成FortuneThread的介绍,这是设置quit为true的析构函数,唤醒线程,并在返回之前等待线程退出。这允许run()中的while循环完成当前迭代。当run()返回时,线程将终止并被销毁。
现在来看看BlockingClient类
class BlockingClient : public QWidget { Q_OBJECT public: BlockingClient(QWidget *parent = nullptr); private slots: void requestNewFortune(); void showFortune(const QString &fortune); void displayError(int socketError, const QString &message); void enableGetFortuneButton(); private: QLabel *hostLabel; QLabel *portLabel; QLineEdit *hostLineEdit; QLineEdit *portLineEdit; QLabel *statusLabel; QPushButton *getFortuneButton; QPushButton *quitButton; QDialogButtonBox *buttonBox; FortuneThread thread; QString currentFortune; };
BlockingClient 类与《Fortune Client》示例中的 Client 类非常相似,但在本类中我们存储了一个 FortuneThread 成员,而不是指向 QTcpSocket 的指针。当用户点击“获取幸运语”按钮时,会调用相同的槽函数,但其实现略有不同。
connect(&thread, &FortuneThread::newFortune, this, &BlockingClient::showFortune); connect(&thread, &FortuneThread::error, this, &BlockingClient::displayError);
我们将我们的 FortuneThread 的两个信号 newFortune() 和 error()(在先前的示例中类似于 QTcpSocket::readyRead() 和 QTcpSocket::error())连接到 requestNewFortune() 和 displayError()。
void BlockingClient::requestNewFortune() { getFortuneButton->setEnabled(false); thread.requestNewFortune(hostLineEdit->text(), portLineEdit->text().toInt()); }
requestNewFortune() 槽函数调用 FortuneThread::requestNewFortune(),该函数对请求进行调度。当线程接收到新的幸运语并发出 newFortune() 信号时,我们的 showFortune() 槽函数被调用。
void BlockingClient::showFortune(const QString &nextFortune) { if (nextFortune == currentFortune) { requestNewFortune(); return; } currentFortune = nextFortune; statusLabel->setText(currentFortune); getFortuneButton->setEnabled(true); }
在这里,我们只简单地将接收到的幸运语作为参数显示出来。
另请参阅Fortune Client 和 Fortune Server。
© 2024 The Qt Company Ltd. 本文档中包含的贡献的版权属于其各自的版权所有者。本文档依据由 Free Software Foundation 发布的 GNU Free Documentation License 版本 1.3 的条款提供许可。Qt 及其相关标志是 The Qt Company Ltd. 在芬兰和其他国家/地区的商标。所有其他商标均为其各自所有者的财产。