本系列所有文章可以在這里查看[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例程就說到這里啦~
- 前言
- 1——Fortune Server/Client
- 2——Multicast Sender/Receiverz
- 3——Broadcast Sender/Receiver
- 4——Blocking Fortune Client
- 5——Threaded Fortune Server
- 5(總結)——Fortune例程的各個實現區別
- 6——Loopback Example
- 7——Analog Clock Example
- 8——Shaped Clock Example
- 9——Analog Clock Window Example
- 10——Qt Quick Particles Examples - Emitters
- 11——Qt Quick Particles Examples - Affectors
- 12——Qt Quick Particles Examples - CustomParticles
- 13——Qt Quick Particles Examples - Image Particles
- 14——Qt Quick Particles Examples - System
- 15——Chapter 1: Creating a New Type
- 16——Chapter 2: Connecting to C++ Methods and Signals
- 17——Chapter 3: Adding Property Bindings
- 18——Chapter 4: Using Custom Property Types
- 19——Chapter 5: Using List Property Types
- 20——Chapter 6: Writing an Extension Plugin
- 21——Extending QML - Adding Types Example
- 22——Extending QML - Object and List Property Types Example
- 23——Extending QML - Inheritance and Coercion Example
- 24——Extending QML - Default Property Example
- 25——Extending QML - Methods Example
- 26——Extending QML - Grouped Properties Example
- 27——Extending QML - Attached Properties Example
- 28——Extending QML - Signal Support Example
- 29——Extending QML - Property Value Source Example
- 30——Extending QML - Binding Example
- 31——StocQt
- 32——Qt Quick Examples - Threading
- 33——Qt Quick Examples - Window and Screen
- 34——Concentric Circles Example
- 35——Music Player
- 36——Wiggly Example
- 37——Vector Deformation