4——Blocking Fortune Client

最后更新于:2022-04-01 07:21:11

本系列所有文章可以在这里查看[http://blog.csdn.net/cloud_castle/article/category/2123873](http://blog.csdn.net/cloud_castle/article/category/2123873) 这一章只讲客户端。这个例子与之前的Fortune Client非常相似,但QTcpsocket在这里并不是作为主类的一个成员,而是在一个单独的线程中使用QTcpsocket的blocking API做所有的网络操作。因为这个操作是阻塞的,所以不能放在GUI线程中。这个客户端是可以和Fortune Server配合使用的。文件比较多,一共有5个。 main.cpp: ~~~ #include <QApplication> #include "blockingclient.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); BlockingClient client; client.show(); return app.exec(); } ~~~ fortunethread.h: ~~~ #ifndef FORTUNETHREAD_H #define FORTUNETHREAD_H #include <QThread> #include <QMutex> // 互斥锁,用来保护线程安全 #include <QWaitCondition> // 提供一个或多个线程的唤醒条件 //! [0] class FortuneThread : public QThread { Q_OBJECT public: FortuneThread(QObject *parent = 0); ~FortuneThread(); void requestNewFortune(const QString &hostName, quint16 port); void run(); signals: void newFortune(const QString &fortune); void error(int socketError, const QString &message); private: QString hostName; quint16 port; QMutex mutex; QWaitCondition cond; bool quit; }; //! [0] #endif ~~~ fortunethread.cpp: ~~~ #include <QtNetwork> #include "fortunethread.h" FortuneThread::FortuneThread(QObject *parent) : QThread(parent), quit(false) { } //! [0] FortuneThread::~FortuneThread() { mutex.lock(); // 设置互斥锁为了对成员quit进行写操作 quit = true; // 为了退出run()中的while(!quit) cond.wakeOne(); // 唤醒线程 mutex.unlock(); // 解锁 wait(); // 等待run()返回,接着线程被析构 } //! [0] //! [1] //! [2] void FortuneThread::requestNewFortune(const QString &hostName, quint16 port) { //! [1] QMutexLocker locker(&mutex); // QMutex的一个便利类,创建时相当于mutex.lock(),被销毁时相当于mutex.unlock(),在写复杂代码时很有效(局部变量的生存期) this->hostName = hostName; // 第一个hostName是线程成员变量,第二个是局部变量,是传入参数的引用,别被名字搞混了 this->port = port; //! [3] if (!isRunning()) start(); else cond.wakeOne(); // 唤醒一个满足等待条件的线程,这个线程即是被cond.wait()挂起的线程 } //! [2] //! [3] //! [4] void FortuneThread::run() // 多线程最重要的就是run()函数了 { mutex.lock(); // 为什么要锁?有可能requestNewFortune同时在写入这些数据 //! [4] //! [5] QString serverName = hostName; quint16 serverPort = port; mutex.unlock(); //! [5] //! [6] while (!quit) { // 只要quit为false就一直循环 //! [7] const int Timeout = 5 * 1000; // 5秒延时。这里用Timeout、5 * 1000 而不是 5000 使程序清晰易读 QTcpSocket socket; // 与fortune Client不同,这里的QTcpSocket创建在栈上 socket.connectToHost(serverName, serverPort); // connectToHost还是个异步操作 //! [6] //! [8] if (!socket.waitForConnected(Timeout)) { // 在Timeout内成功建立连接返回true,默认参数为30s。这个函数就是QTcpSocket的一个Blocking API了 emit error(socket.error(), socket.errorString()); // 发送自定义信号 return; } //! [8] //! [9] while (socket.bytesAvailable() < (int)sizeof(quint16)) { // 是否连接上了但没有数据 if (!socket.waitForReadyRead(Timeout)) { // 如果5s内没有可供阅读的数据则报错(又将线程阻塞5秒) emit error(socket.error(), socket.errorString()); return; } //! [9] //! [10] } //! [10] //! [11] quint16 blockSize; // 现在开始读取数据 QDataStream in(&socket); in.setVersion(QDataStream::Qt_4_0); in >> blockSize; // 数据长度赋予blockSize //! [11] //! [12] while (socket.bytesAvailable() < blockSize) { // 关于Tcp分段传输,参见Fortune Sender/Client if (!socket.waitForReadyRead(Timeout)) { // 这里主要为了等待数据达到blockSize的长度 emit error(socket.error(), socket.errorString()); return; } //! [12] //! [13] } //! [13] //! [14] mutex.lock(); QString fortune; in >> fortune; emit newFortune(fortune); // 自定义信号,newFortune被emit //! [7] //! [14] //! [15] cond.wait(&mutex); // 现在将线程挂起,挂起状态应该在lock与unlock之间,等待cond.wakeOne()或者cond.wakeAll()唤醒 serverName = hostName; // 被唤醒后从这里开始,继续while(!quit)循环 serverPort = port; mutex.unlock(); } //! [15] } ~~~ 呼~大头结束了,主类就好说了,大部分代码与Fortune Client中都是相似的,就不细讲了。可以参考Qt官方demo解析集1——Fortune Client部分 blockingclient.h: ~~~ #ifndef BLOCKINGCLIENT_H #define BLOCKINGCLIENT_H #include <QWidget> #include "fortunethread.h" QT_BEGIN_NAMESPACE class QDialogButtonBox; class QLabel; class QLineEdit; class QPushButton; class QAction; QT_END_NAMESPACE //! [0] class BlockingClient : public QWidget { Q_OBJECT public: BlockingClient(QWidget *parent = 0); 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; // QTcpSocket的指针没有了,变成了一个FortuneThread类成员 QString currentFortune; }; //! [0] #endif ~~~ blockingclient.cpp: ~~~ #include <QtWidgets> #include <QtNetwork> #include <QDebug> #include "blockingclient.h" BlockingClient::BlockingClient(QWidget *parent) : QWidget(parent) { hostLabel = new QLabel(tr("&Server name:")); portLabel = new QLabel(tr("S&erver port:")); // find out which IP to connect to QString ipAddress; QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses(); // use the first non-localhost IPv4 address for (int i = 0; i < ipAddressesList.size(); ++i) { // 第(1)点 if (ipAddressesList.at(i) != QHostAddress::LocalHost && ipAddressesList.at(i).toIPv4Address()) { ipAddress = ipAddressesList.at(i).toString(); break; } } // if we did not find one, use IPv4 localhost if (ipAddress.isEmpty()) ipAddress = QHostAddress(QHostAddress::LocalHost).toString(); hostLineEdit = new QLineEdit(ipAddress); portLineEdit = new QLineEdit; portLineEdit->setValidator(new QIntValidator(1, 65535, this)); hostLabel->setBuddy(hostLineEdit); // 上次没提这个,因为LineEdit不方便设置快捷键,往往由前面Label的setBuddy绑起来。例如这里是Alt+s portLabel->setBuddy(portLineEdit); statusLabel = new QLabel(tr("This examples requires that you run the " "Fortune Server example as well.")); statusLabel->setWordWrap(true); getFortuneButton = new QPushButton(tr("Get Fortune")); getFortuneButton->setDefault(true); getFortuneButton->setEnabled(false); quitButton = new QPushButton(tr("Quit")); buttonBox = new QDialogButtonBox; buttonBox->addButton(getFortuneButton, QDialogButtonBox::ActionRole); buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole); connect(getFortuneButton, SIGNAL(clicked()), this, SLOT(requestNewFortune())); connect(quitButton, SIGNAL(clicked()), this, SLOT(close())); connect(hostLineEdit, SIGNAL(textChanged(QString)), this, SLOT(enableGetFortuneButton())); connect(portLineEdit, SIGNAL(textChanged(QString)), this, SLOT(enableGetFortuneButton())); //! [0] connect(&thread, SIGNAL(newFortune(QString)), // 这是线程中自定义的信号 this, SLOT(showFortune(QString))); //! [0] //! [1] connect(&thread, SIGNAL(error(int,QString)), // 同上 this, SLOT(displayError(int,QString))); //! [1] QGridLayout *mainLayout = new QGridLayout; mainLayout->addWidget(hostLabel, 0, 0); mainLayout->addWidget(hostLineEdit, 0, 1); mainLayout->addWidget(portLabel, 1, 0); mainLayout->addWidget(portLineEdit, 1, 1); mainLayout->addWidget(statusLabel, 2, 0, 1, 2); mainLayout->addWidget(buttonBox, 3, 0, 1, 2); setLayout(mainLayout); setWindowTitle(tr("Blocking Fortune Client")); portLineEdit->setFocus(); } //! [2] void BlockingClient::requestNewFortune() // 直接调用thread的requestNewFortune函数,注意线程中这个函数是需要两个参数的 { getFortuneButton->setEnabled(false); thread.requestNewFortune(hostLineEdit->text(), portLineEdit->text().toInt()); } //! [2] //! [3] void BlockingClient::showFortune(const QString &nextFortune) { if (nextFortune == currentFortune) { requestNewFortune(); return; } //! [3] //! [4] currentFortune = nextFortune; statusLabel->setText(currentFortune); getFortuneButton->setEnabled(true); } //! [4] void BlockingClient::displayError(int socketError, const QString &message) // 整型sockError指向QAbstraction的emun量,方便使用switch跳转 { switch (socketError) { case QAbstractSocket::HostNotFoundError: QMessageBox::information(this, tr("Blocking Fortune Client"), tr("The host was not found. Please check the " "host and port settings.")); break; case QAbstractSocket::ConnectionRefusedError: QMessageBox::information(this, tr("Blocking 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("Blocking Fortune Client"), tr("The following error occurred: %1.") .arg(message)); } getFortuneButton->setEnabled(true); } void BlockingClient::enableGetFortuneButton() { bool enable(!hostLineEdit->text().isEmpty() && !portLineEdit->text().isEmpty()); // 这个表达式很有用,这里的enable不是函数名,而是一个bool型的变量名 getFortuneButton->setEnabled(enable); } ~~~ 第一点,这个程序我在自己机器上面运行的时候程序取到的都是169.254,即DHCP动态分配失败而给机器分配的IP地址,我对网络不是很熟,只知道这个IP地址肯定通讯不了的,事实也是这样。通过改变(1)里面 i 的初始值可以解决这个问题,但并不是一个理想的解决方案。不过这个问题应该是个人机器问题,因为在工作电脑上调试时没出现这个问题。姑且记录在此吧。 好了,Blocking Fortune Client例程就说到这里啦~
';