Qt5官方Demo解析集4——Blocking Fortune Client

本系列所有文章可以在这里查看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例程就说到这里啦~






你可能感兴趣的:(qt,NetWork,Qt5官方Demo解析集)