Trip Planner客户端的实现

                      Trip Planner客户端的实现

QTcpSocket QTcpServer类可以用来实现TCP客户端和服务器,TCP是一个基于流的协议,对于应用程序,数据表现为长长的流,而不是一个大的平面文件,在TCP之上建立的高层协议通常是基于行或者基于块的。

Trip Planner客户端的实现_第1张图片

                                               1                                                                   

我们看上面这个类继承架构图可以很清楚的看到QTCPSocket间接的由QIODevice派生而来,所以它可以使用QDataStream或者QTextStream来进行读取和写入。

下面我们将构建一个如下图所示的Trip Planner客户端,Trip Server服务器我们将在下一章中讲述。

 Trip Planner客户端的实现_第2张图片

      

                                                2Trip Planner               

 

利用QT设计师,创建一个名为tripplanner.ui的界面文件,这里我们不讨论界面创建的详细过程,我们把重点放在客户端的相关代码。

 

   connect(searchButton, SIGNAL(clicked()), this, SLOT(connectToServer()));

   connect(stopButton, SIGNAL(clicked()), this, SLOT(stopSearch()));

   connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));

 

   connect(&tcpSocket, SIGNAL(connected()), this, SLOT(sendRequest()));

   connect(&tcpSocket, SIGNAL(disconnected()),

           this, SLOT(connectionClosedByServer()));

   connect(&tcpSocket, SIGNAL(readyRead()),

           this, SLOT(updateTableWidget()));

   connect(&tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)),

           this, SLOT(error()));

这是构造函数中的代码,除了将这里的按钮连接到私有槽函数外,还把TcpSocketconnected()disconnected() readyRead()、和error(QAbstractSocket::SocketError)信号与私有槽函数连接起来。

Connected()信号在当调用connectToHost()函数并且有一个连接成功后就会发射这个信号,否则,就会发射disconnected()信号。

readyRead()当有新的有效的数据从device读取时,就会发射readyRead()信号。

error(QAbstractSocket::SocketError)当有error发生时就会发射这个信号,QAbstractSocket::SocketError描述错误的类型。

 

void TripPlanner::connectToServer()

{

   tcpSocket.connectToHost(QHostAddress::LocalHost, 6178);

   tableWidget->setRowCount(0);

   searchButton->setEnabled(false);

   stopButton->setEnabled(true);

   statusLabel->setText(tr("Connecting to server..."));

   progressBar->show();

   nextBlockSize = 0;

}

当用户单击search按钮时,就会执行这个槽函数,首先,我们在TckSocket对象上调用connectToHost()从而连接到服务器,这里我们的地址选择LocalHost,当然你也可以选择LocalHostIPv6等。

 

void TripPlanner::sendRequest()

{

   QByteArray block;

   QDataStream out(&block, QIODevice::WriteOnly);

   out.setVersion(QDataStream::Qt_4_3);

   out << quint16(0) <currentText()

       << toComboBox->currentText() << dateEdit->date()

       << timeEdit->time();

   if(departureRadioButton->isChecked()) {

       out << quint8('D');

   } else {

       out << quint8('A');

   }

   out.device()->seek(0);

   out << quint16(block.size() - sizeof(quint16));

   tcpSocket.write(block);

   statusLabel->setText(tr("Sending Request..."));

}

 

这里我们需要请求的数据格式如下:

 Trip Planner客户端的实现_第3张图片

TcpSocket发射connected()信号时,就会调用sendRequest()这个槽函数,首先把数据写到block(QByteArray)中。设置好数据之后,在通过tcpSocket.write()发送这个块。

 

void TripPlanner::updateTableWidget()

{

   QDataStream in(&tcpSocket);

   in.setVersion(QDataStream::Qt_4_3);

   forever{

       int row = tableWidget->rowCount();

       if(nextBlockSize == 0) {

           if(tcpSocket.bytesAvailable() < sizeof(quint16))

               break;

           in >> nextBlockSize;

       }

       if(nextBlockSize == 0xFFFF) {

           closeConnection();

           statusLabel->setText(tr("Found %1 trip(s)").arg(row));

           break;

       }

       if(tcpSocket.bytesAvailable() < nextBlockSize)

           break;

 

       QDate date;

       QTime departureTime;

       QTime arrivalTime;

       quint16 duration;

       quint8 changes;

       QString trainType;

 

       in >> date >>departureTime >>duration >> changes >>trainType;

       arrivalTime = departureTime.addSecs(duration * 60);

       tableWidget->setRowCount(row + 1);

 

       QStringList fields;

       fields << date.toString(Qt::LocalDate)

              << departureTime.toString(tr("hh::mm"))

              << arrivalTime.toString(tr("hh::mm"))

              << tr("%1 hr %2 min").arg(duration / 60)

                                   .arg(duration % 60)

              << QString::number(changes)

              <

       for(int i = 0; i < fields.count(); ++i)

           tableWidget->setItem(row, i,

                                new QTableWidgetItem(fields[i]));

       nextBlockSize = 0;

   }

}

 

只要QTcpSocket已经从服务器收到新数据,就会发送改信号。

从服务器上接收的块具有如下格式:

Trip Planner客户端的实现_第4张图片

 

tcpSocket.bytesAvailable()返回读取有效数据的字节数并与块大小进行比较,如果比块小则返回。

我们看看Forever()循环里面如何工作:

由于刚开始并不知道block的大小,所以第一次设置为0,后面用实际大小覆盖,那么读取如果为0,就意味着还没有读取到数据,服务器用一个大小为0XFFFF表示没有更多的数据读取。一旦读取到正确的数据,就用>>提取数据并显示在TableWidget中。

 

void TripPlanner::connectionClosedByServer()

{

   if(nextBlockSize != 0xFFFF)

       statusLabel->setText(tr("Error:Connection close by server.."));

   closeConnection();

}

 

void TripPlanner::error()

{

   statusLabel->setText(tcpSocket.errorString());

   closeConnection();

}

 

void TripPlanner::stopSearch()

{

   statusLabel->setText(tr("Search Stop."));

   closeConnection();

}

 

void TripPlanner::closeConnection()

{

   tcpSocket.close();

   searchButton->setEnabled(true);

   stopButton->setEnabled(false);

   progressBar->hide();

}           

那么上面的这些实现主要是关闭TcpSocket并且设置相关按钮的状态以及状态栏目的提示信息。

 

#include

#include "tripplanner.h"

 

int main(int argc, char *argv[])

{

   QApplication app(argc, argv);

   TripPlanner tripPlanner;

   tripPlanner.show();

 

   return app.exec();

}

最后main函数里面创建并显示这个对象就可以看到效果了。

 

你可能感兴趣的:(QT,C/C++)