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例程就说到这里啦~