Qt-Fortune Client Example-翻译

Demonstrates how to create a client for a network service

This example uses QTcpSocket, and is intended to be run alongside the Fortune Server example or the Threaded Fortune Server example.

说明如何未一个网络服务创建一个client,这个例子使用了QTcpSocket,需要与Fortune Server或Threaded Fortune Server一同运行。


This example uses a simple QDataStream-based data transfer protocol to request a line of text from a fortune server (from the Fortune Server example). The client requests a fortune by simply connecting to the server. The server then responds with a 16-bit (quint16) integer containing the length of the fortune text, followed by a QString.

这个例子使用了一个简单的基于QDataStream数据传输协议来从fortune server请求一行text(从Fortune Server例子)。客户端通过简单的连接服务器来请求一个fortune。服务器就反馈一个16bit的整数,随后一个包含该长度的fortune text(是QString类型)。

QTcpSocket supports two general approaches to network programming:

  • The asynchronous (non-blocking) approach. Operations are scheduled and performed when control returns to Qt's event loop. When the operation is finished, QTcpSocketemits a signal. For example, QTcpSocket::connectToHost() returns immediately, and when the connection has been established, QTcpSocket emits connected().
  • The synchronous (blocking) approach. In non-GUI and multithreaded applications, you can call the waitFor...() functions (e.g., QTcpSocket::waitForConnected()) to suspend the calling thread until the operation has completed, instead of connecting to signals.
qt支持两种网络编程方法:

异步方法:控制返回qt的事件loop后安排和执行operations,当operations结束后,qtcpsocket发射一个信号。例如,qtcpsocket::connecttohost()一返回,连接建立,qtcpsocket发射连接的信号。

同步方法:在非gui和多进程应用中,可以调用waitfor...()函数,如qtcpsocket::waitforconnected()来悬挂调用进程直到完成operations,而不是发射信号。

class Client : public QDialog
{
    Q_OBJECT

public:
    Client(QWidget *parent = 0);

private slots:
    void requestNewFortune();
    void readFortune();
    void displayError(QAbstractSocket::SocketError socketError);
    void enableGetFortuneButton();
    void sessionOpened();

private:
    QLabel *hostLabel; // server name 标签
    QLabel *portLabel; // server port 标签
    QComboBox *hostCombo; // host下拉列表
    QLineEdit *portLineEdit; // port编辑框
    QLabel *statusLabel; // 状态显示栏
    QPushButton *getFortuneButton; // get fortune按钮
    QPushButton *quitButton; // 退出按钮
    QDialogButtonBox *buttonBox; // 用于封装get fortune和quit按钮

    QTcpSocket *tcpSocket; // 套接字
    QString currentFortune; // 字符串用于显示于状态显示栏
    quint16 blockSize; // 块大小

    QNetworkSession *networkSession; // 网络session
};

Other than the widgets that make up the GUI, the data members include a QTcpSocket pointer, a copy of the fortune text currently displayed, and the size of the packet we are currently reading (more on this later).

The socket is initialized in the Client constructor. We'll pass the main widget as parent, so that we won't have to worry about deleting the socket:

不是widgets建立gui,数据成员,包括qtcpsocket指针,显示fortune text的一个副本,及将阅读包的大小。socket在client的构造函数中初始化,作为parent传送给main widget,因此而不用检测socket。

Client::Client(QWidget *parent)
:   QDialog(parent), networkSession(0)
{
    ...
    tcpSocket = new QTcpSocket(this); // 构造函数里面初始化了tcpsocket对象
The only QTcpSocket signals we need in this example are QTcpSocket::readyRead(), signifying that data has been received, and QTcpSocket::error(), which we will use to catch any connection errors:

唯一需要的qtcpsocket信号是qtcpsocket::readyread(),标示数据已经接受,而qtcpsocket::error()用来捕捉连接错误。
   ...
    connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(readFortune())); // 绑定信号,接受完毕后读取fortune()
    connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this,SLOT(requestNewFortune())); // 绑定错误,请求新的fortune
Clicking the Get Fortune button will invoke the requestNewFortune() slot:

点击get fortune按钮会唤起requestNewFortune()槽:

void Client::requestNewFortune()
{
    getFortuneButton->setEnabled(false); // 屏蔽后续请求
    blockSize = 0; // 初始化包大小为0
    tcpSocket->abort(); // 结束tcpsocket
    tcpSocket->connectToHost(hostCombo->currentText(),
                             portLineEdit->text().toInt()); // 通过目标信息连接到host中
}
In this slot, we initialize blockSize to 0, preparing to read a new block of data. Because we allow the user to click Get Fortune before the previous connection finished closing, we start off by aborting the previous connection by calling QTcpSocket::abort(). (On an unconnected socket, this function does nothing.) We then proceed to connecting to the fortune server by calling QTcpSocket::connectToHost(), passing the hostname and port from the user interface as arguments.

在这个slot中,我们初始化blocksize为0,准备读取一个新的块数据,由于允许用户在前一个连接完成关闭之前点击get fortune,因此,用absort()终止前一个连接为开端。(在一个未连接的socket,这个函数不做任何事情)然后,就着手用connectToHost()连接fortune服务器,从界面参数传递hostname和端口参数。
As a result of calling connectToHost(), one of two things can happen:

The connection is established. In this case, the server will send us a fortune. QTcpSocket will emit readyRead() every time it receives a block of data.
An error occurs. We need to inform the user if the connection failed or was broken. In this case, QTcpSocket will emit error(), and Client::displayError() will be called.
Let's go through the error() case first:

调用了connectToHost()函数,意味着两件事情的发生。

连接建立,这种情况下,服务器发送给我们一个fortune,QTcpSocket每接受到一个块数据就发射一次readyRead()
发生错误,需要通知用户连接失败,这种情况下,QTcpSocket发射error(),同时Client::displayError()会被调用。

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);
}

We pop up all errors in a dialog using QMessageBox::information(). QTcpSocket::RemoteHostClosedError is silently ignored, because the fortune server protocol ends with the server closing the connection.

Now for the readyRead() alternative. This signal is connected to Client::readFortune():

用QMessageBox::information()输出所有错误,QTcpSocket::RemoteHostClosedError 一定程度会忽略,由于fortune服务器协议与远程服务器关闭一同停止。

然后readyRead()是可选的,信号连接连接到了Client::readFortune();

void Client::readFortune()
{
    QDataStream in(tcpSocket); // 将套接字的数据读入QDataStream中
    in.setVersion(QDataStream::Qt_4_0); // 设置读入QDataStream的版本::Qt_4_0

    if (blockSize == 0) {  // 读入一个quint16都不行,就可以直接返回了
        if (tcpSocket->bytesAvailable() < (int)sizeof(quint16))
            return;

        in >> blockSize; // 从in数据流读入到blockSize中
    }

    if (tcpSocket->bytesAvailable() < blockSize) // 目标读取的长度大于可供读写长度
        return;
The protocol is based on QDataStream, so we start by creating a stream object, passing the socket to QDataStream's constructor. We then explicitly set the protocol version of the stream to QDataStream::Qt_4_0 to ensure that we're using the same version as the fortune server, no matter which version of Qt the client and server use.

Now, TCP is based on sending a stream of data, so we cannot expect to get the entire fortune in one go. Especially on a slow network, the data can be received in several small fragments. QTcpSocket buffers up all incoming data and emits readyRead() for every new block that arrives, and it is our job to ensure that we have received all the data we need before we start parsing. The server's response starts with the size of the packet, so first we need to ensure that we can read the size, then we will wait until QTcpSocket has received the full packet.

这个协议是给予QDataStream,因此开始就创建了一个stream对象,传递socket到QDataStream的构造函数中,然后明确设置stream协议的版本以保证使用的与server相同的协议,无论哪个Qt协议都可以的。
现在,TCP基于发送一个流数据,因此不能期待一次性得到完整的fortune,尤其是缓慢网络的时候,数据会在数个时间片收到,QTcpSocket缓冲区缓冲所有传来的数据然后在每个新的块数据到达时候发射readRead(),保证在执行解析前收到所需的所有数据。服务器的响应以包大小为开头,因此,我们需要确保可以读取到packetsize,然后等待直到QTcpSocket接收到整个包。
 QString nextFortune;
    in >> nextFortune;

    if (nextFortune == currentFortune) {
        QTimer::singleShot(0, this, SLOT(requestNewFortune()));
        return;
    }

    currentFortune = nextFortune;
    statusLabel->setText(currentFortune);
    getFortuneButton->setEnabled(true);
}
We proceed by using QDataStream's streaming operator to read the fortune from the socket into a QString. Once read, we can call QLabel::setText() to display the fortune.
用QDataStream的流运算来从socket读取fortune到一个QString,每读取一次,就调用QLabel::setText()来显示fortune。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章