福布斯客户端
演示如何为网络服务创建客户端。
此示例使用QTcpSocket,旨在与福布斯服务器示例或线程化福布斯服务器示例一起运行。
此示例使用一种基于QDataStream的数据传输协议,从福布斯服务器(来自福布斯服务器示例)请求一行文本。客户端通过简单地连接到服务器来请求福气。服务器随后响应一个包含福气文本的QString。
QTcpSocket支持两种网络编程的通用方法
- 异步(非阻塞)方法。操作将在控制权返回Qt的事件循环时安排和执行。操作完成后,QTcpSocket会发出一个信号。例如,QTcpSocket::connectToHost()将立即返回,并且在连接建立后,QTcpSocket会发出connected()。
- 同步(阻塞)方法。在非GUI和单线程应用程序中,你可以调用
waitFor...()
函数(例如,QTcpSocket::waitForConnected())来挂起调用线程,直到操作完成,而不是连接到信号。
在这个示例中,我们将演示异步方法。使用Blocking Fortune Client示例说明同步方法。
我们的类包含一些数据和几个私有槽
class Client : public QDialog { Q_OBJECT public: explicit Client(QWidget *parent = nullptr); private slots: void requestNewFortune(); void readFortune(); void displayError(QAbstractSocket::SocketError socketError); void enableGetFortuneButton(); private: QComboBox *hostCombo = nullptr; QLineEdit *portLineEdit = nullptr; QLabel *statusLabel = nullptr; QPushButton *getFortuneButton = nullptr; QTcpSocket *tcpSocket = nullptr; QDataStream in; QString currentFortune; };
除了构成GUI的小部件之外,数据成员还包括一个QTcpSocket指针、一个在套接字上操作QDataStream对象以及当前显示的福气文本副本。
套接字在客户端构造函数中初始化。我们将以主小部件作为父对象,这样我们就不用担心删除套接字了
Client::Client(QWidget *parent) : QDialog(parent) , hostCombo(new QComboBox) , portLineEdit(new QLineEdit) , getFortuneButton(new QPushButton(tr("Get Fortune"))) , tcpSocket(new QTcpSocket(this)) { ... in.setDevice(tcpSocket); in.setVersion(QDataStream::Qt_6_5);
该协议基于QDataStream,因此我们将流设备设置为新创建的套接字。然后我们显式地将流的协议版本设置为QDataStream::Qt_6_5以确保无论客户端和服务器使用哪个版本的Qt,我们都在使用相同的版本。
在这个示例中,我们需要使用的唯一QTcpSocket信号是表示已接收数据的QTcpSocket::readyRead()以及我们将用以捕获任何连接错误的QTcpSocket::errorOccurred()
... connect(tcpSocket, &QIODevice::readyRead, this, &Client::readFortune); connect(tcpSocket, &QAbstractSocket::errorOccurred, ... }
点击获取福气按钮将触发requestNewFortune()
槽
void Client::requestNewFortune() { getFortuneButton->setEnabled(false); tcpSocket->abort(); tcpSocket->connectToHost(hostCombo->currentText(), portLineEdit->text().toInt()); }
因为我们允许用户在先前的连接完成关闭之前点击“获取幸运”,所以我们首先通过调用QTcpSocket::abort()来终止先前的连接。(在一个未连接的套接字上,此函数不起作用。)然后,我们通过调用QTcpSocket::connectToHost()来连接到幸运服务器,将用户界面提供的主机名和端口号作为参数传递。
调用connectToHost()可能导致以下两种情况之一
- 连接已建立。在这种情况下,服务器将向我们发送一个幸运信息。《a href="qtcpsocket.html" translate="no">QTcpSocket将每次接收到数据块时发出readyRead()。
- 发生错误。如果连接失败或中断,我们需要通知用户。在这种情况下,QTcpSocket将发出errorOccurred(),并将调用Client的displayError()。
让我们首先分析errorOccurred()情况
void Client::displayError(QAbstractSocket::SocketError socketError) { switch (socketError) { case QAbstractSocket::RemoteHostClosedError: break; case QAbstractSocket::HostNotFoundError: QMessageBox::information(this, tr("Fortune Client"), tr("The host was not found. Please check the " "host name and port settings.")); break; case QAbstractSocket::ConnectionRefusedError: QMessageBox::information(this, tr("Fortune Client"), tr("The connection was refused by the peer. " "Make sure the fortune server is running, " "and check that the host name and port " "settings are correct.")); break; default: QMessageBox::information(this, tr("Fortune Client"), tr("The following error occurred: %1.") .arg(tcpSocket->errorString())); } getFortuneButton->setEnabled(true); }
我们使用QMessageBox::information对话框弹出所有错误。QTcpSocket::RemoteHostClosedError被静默忽略,因为幸运服务器协议以服务器关闭连接结束。
现在,让我们来看看readyRead()替代方案。此信号连接到Client::readFortune()
void Client::readFortune() { in.startTransaction(); QString nextFortune; in >> nextFortune; if (!in.commitTransaction()) return; if (nextFortune == currentFortune) { QTimer::singleShot(0, this, &Client::requestNewFortune); return; } currentFortune = nextFortune; statusLabel->setText(currentFortune); getFortuneButton->setEnabled(true); }
现在,TCP基于发送数据流,因此我们无法期望一次性接收到整个幸运信息。尤其是在低速网络中,数据可以分成几个小片段接收。QTcpSocket将累积所有传入的数据,并为每个到达的新数据块发出readyRead(),确保我们在开始解析之前收到了所有需要的数据。
为此,我们使用QDataStream读取事务。它将流数据持续读取到内部缓冲区,并在不完整读取的情况下回滚。我们首先调用startTransaction(),这也会重置流状态,以指示套接字上接收到了新的数据。然后,我们使用QDataStream的流运算符从套接字读取幸运信息到一个QString。一旦读取完毕,我们通过调用QDataStream::commitTransaction来完成事务。如果我们没有收到完整的包,此函数会将流数据恢复到初始位置,之后我们可以等待新的readyRead()信号。
读取事务成功后,我们调用QLabel::setText()来显示幸运信息。
© 2024 The Qt Company Ltd。包括在内的文档贡献是各自所有者的版权。《本文档》根据Free Software Foundation发布的版本1.3的GNU自由文档许可证的条款获得许可。Qt和相应商标是芬兰的The Qt Company Ltd.以及世界其他国家的商标。所有其他商标均为其各自所有者的财产。