Trip Planner客户端的实现
QTcpSocket和 QTcpServer类可以用来实现TCP客户端和服务器,TCP是一个基于流的协议,对于应用程序,数据表现为长长的流,而不是一个大的平面文件,在TCP之上建立的高层协议通常是基于行或者基于块的。
图1
我们看上面这个类继承架构图可以很清楚的看到QTCPSocket间接的由QIODevice派生而来,所以它可以使用QDataStream或者QTextStream来进行读取和写入。
下面我们将构建一个如下图所示的Trip Planner客户端,Trip Server服务器我们将在下一章中讲述。
图2:Trip 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()));
这是构造函数中的代码,除了将这里的按钮连接到私有槽函数外,还把TcpSocket的connected()、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) <
<< 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..."));
}
这里我们需要请求的数据格式如下:
当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已经从服务器收到新数据,就会发送改信号。
从服务器上接收的块具有如下格式:
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函数里面创建并显示这个对象就可以看到效果了。