QT TCP下的socket编程
2011-10-24 00:24
295 查看
QTcpSocket和QTcpServer类实现了Qt的Tcp客户端和服务器。
tcp是一个流式协议。对于应用程序来说,数据是一个很长的流,有点像一个巨大的文件。
搞成此的协议建立在面向块的tcp协议(Block-oriented)或面向行(Line-oriented)的tcp协议上。
面向块的tcp协议,数据被当作一个2进制的块来传输。没每一个块被当作一个定义了大小的,后面跟随了数据的字段。
面向行的tcp协议,数据被当作一个文本文件的一行。一个传输终止于一个新的行的到来。
QTcpSocket继承自QIODevice,所以它可以从QDataStream或QTextStream中读取或写入数据。
从文件读数据和从网络上读数据有一个明显的不同点:我们必须保证用“>>”操作符读取数据时,已经从另一方接收了足够的数据。如果你这样做了,那么一个失败的结果是:行为未定义。
我们来看一个使用block-orientedtcp协议的服务器和客户端的代码。
用户填写行程的起始地,目的地,日期等,服务器返回符合要求的行程。
界面用QDesigner设计的。叫做“tripplanner.ui”。
请使用uic工具转换。
include"ui_tripplanner.h"
classTripPlanner:publicQDialog,publicUi::TripPlanner
{
Q_OBJECT
public:
TripPlanner(QWidget*parent=0);
privateslots:
voidconnectToServer();
voidsendRequest();
voidupdateTableWidget();
voidstopSearch();
voidconnectionClosedByServer();
voiderror();
private:
voidcloseConnection();
QTcpSockettcpSocket;
quint16nextBlockSize;
};
tcpSocket变量是QTcpSocket类型,用来建立一个tcp连接。
当需要提起从服务器传递来的数据块时,nextBlockSize将被使用。
构造函数中,我们设置时间控件的默认属性,隐藏progressBar等。连接tcpSocket的connected(),
disconnected(),readyRead(),
error(QAbstractSocket::SocketError)信号到私有的槽。
voidTripPlanner::stopSearch()
{
statusLabel->setText(tr("Searchstopped"));
closeConnection();
}
如果stopServer按钮被单击,我们关闭连接。[/code]
[/code]
ClientSocket::ClientSocket(QObject*parent)
:QTcpSocket(parent)
{
connect(this,SIGNAL(readyRead()),this,SLOT(readClient()));
connect(this,SIGNAL(disconnected()),this,SLOT(deleteLater()));
nextBlockSize=0;
}
tcp是一个流式协议。对于应用程序来说,数据是一个很长的流,有点像一个巨大的文件。
搞成此的协议建立在面向块的tcp协议(Block-oriented)或面向行(Line-oriented)的tcp协议上。
面向块的tcp协议,数据被当作一个2进制的块来传输。没每一个块被当作一个定义了大小的,后面跟随了数据的字段。
面向行的tcp协议,数据被当作一个文本文件的一行。一个传输终止于一个新的行的到来。
QTcpSocket继承自QIODevice,所以它可以从QDataStream或QTextStream中读取或写入数据。
从文件读数据和从网络上读数据有一个明显的不同点:我们必须保证用“>>”操作符读取数据时,已经从另一方接收了足够的数据。如果你这样做了,那么一个失败的结果是:行为未定义。
我们来看一个使用block-orientedtcp协议的服务器和客户端的代码。
用户填写行程的起始地,目的地,日期等,服务器返回符合要求的行程。
界面用QDesigner设计的。叫做“tripplanner.ui”。
请使用uic工具转换。
include"ui_tripplanner.h"
classTripPlanner:publicQDialog,publicUi::TripPlanner
{
Q_OBJECT
public:
TripPlanner(QWidget*parent=0);
privateslots:
voidconnectToServer();
voidsendRequest();
voidupdateTableWidget();
voidstopSearch();
voidconnectionClosedByServer();
voiderror();
private:
voidcloseConnection();
QTcpSockettcpSocket;
quint16nextBlockSize;
};
tcpSocket变量是QTcpSocket类型,用来建立一个tcp连接。
当需要提起从服务器传递来的数据块时,nextBlockSize将被使用。
TripPlanner::TripPlanner(QWidget*parent) :QDialog(parent) { setupUi(this); QDateTimedateTime=QDateTime::currentDateTime(); dateEdit->setDate(dateTime.date()); timeEdit->setTime(QTime(dateTime.time().hour(),0)); progressBar->hide(); progressBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Ignored); tableWidget->verticalHeader()->hide(); tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); connect(searchButton,SIGNAL(clicked()), this,SLOT(connectToServer())); connect(stopButton,SIGNAL(clicked()),this,SLOT(stopSearch())); 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())); }
构造函数中,我们设置时间控件的默认属性,隐藏progressBar等。连接tcpSocket的connected(),
disconnected(),readyRead(),
error(QAbstractSocket::SocketError)信号到私有的槽。
voidTripPlanner::connectToServer() { tcpSocket.connectToHost("tripserver.zugbahn.de",6178); tableWidget->setRowCount(0); searchButton->setEnabled(false); stopButton->setEnabled(true); statusLabel->setText(tr("Connectingtoserver...")); progressBar->show(); nextBlockSize=0; } 当用户点击searchButton时,connectToServer()槽将被执行。它使用tcpSocket.connectToHost建立到
服务器的连接。connectToServer()槽立即返回。连接的动作实际发生在这之后。当连接建立成功,
QTcpSocket触发connected()信号。如果失败,error()信号被触发。
接着我们设置进度条以及按钮的状态。
把nextBlockSize设置为0.表示我们现在并不知道下一个接收的数据块的大小。
voidTripPlanner::sendRequest()
{
QByteArrayblock;
QDataStreamout(&block,QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_1);
out<<quint16(0)<<quint8('S')<<fromComboBox->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("Sendingrequest..."));
}
当connected()信号被触发,sendRequest()
槽被调用。sendRequest()相爱难过服务器发送一个请求(tcpSocket.write(block))。
我们需要在数据块的第一个字段写入数据块的大小。但是当我们些第一个字段时,我们不知道整个数据块的大小,
所以我们现写入0(out<<quint16(0)).最后,当数据块填充完毕时,我们计算数据块的大小,将指针重新
移动到QDataStream的开头(out.device()->seek(0)),重新写入数据块的大小out<<quint16(block.size()-sizeof(quint16))。
最后,我们发送数据tcpSocket.write(block)。
voidTripPlanner::updateTableWidget()
{
QDataStreamin(&tcpSocket);
in.setVersion(QDataStream::Qt_4_1);
forever{
introw=tableWidget->rowCount();
if(nextBlockSize==0){
if(tcpSocket.bytesAvailable()<sizeof(quint16))
break;
in>>nextBlockSize;
}
if(nextBlockSize==0xFFFF){
closeConnection();
statusLabel->setText(tr("Found%1trip(s)").arg(row));
break;
}
if(tcpSocket.bytesAvailable()<nextBlockSize)
break;
QDatedate;
QTimedepartureTime;
QTimearrivalTime;
quint16duration;
quint8changes;
QStringtrainType;
in>>date>>departureTime>>duration>>changes>>trainType;
arrivalTime=departureTime.addSecs(duration*60);
tableWidget->setRowCount(row+1);
QStringListfields;
fields<<date.toString(Qt::LocalDate)
<<departureTime.toString(tr("hh:mm"))
<<arrivalTime.toString(tr("hh:mm"))
<<tr("%1hr%2min").arg(duration/60)
.arg(duration%60)
<<QString::number(changes)
<<trainType;
for(inti=0;i<fields.count();++i)
tableWidget->setItem(row,i,
newQTableWidgetItem(fields[i]));
nextBlockSize=0;
}
}
当QTcpSocket接收到数据时,readyRead()信号被触发。updateTableWidget()槽就被调用了。
这里我们用了一个forever循环,这是必须的!因为我们无法保证一次就接到了所有的数据块。可能,我们只接收到数据块的一个部分,也可能是全部。
forever循环是如何工作的呢?如果nextBlockSize是0,表示我们没有独到数据块的大小,我们必须重新读取它。数据块的大小字段必须至少读取sizeof(quint16))字节才能获得,如果读取的数据少于sizeof(quint16)),必须重新读取。
如果数据块大小字段为0xFFFF,表示服务器端数据发送完毕,我们停止接收。
最后我们设置nextBlockSize为0,表示下一个数据块的大小还不知道,我们必须接收。
voidTripPlanner::closeConnection()
{
tcpSocket.close();
searchButton->setEnabled(true);
stopButton->setEnabled(false);
progressBar->hide();
}
当接收到的数据块大小字段的值为0xFFFF,我们关闭连接。
{
statusLabel->setText(tr("Searchstopped"));
closeConnection();
}
如果stopServer按钮被单击,我们关闭连接。[/code]
voidTripPlanner::connectionClosedByServer()
{
if(nextBlockSize!=0xFFFF)
statusLabel->setText(tr("Error:Connectionclosedbyserver"));
closeConnection();
}
当服务器断开连接时,如果我们没有读到表示数据传送完毕的0xFFFF,我们发出一个错误。
voidTripPlanner::error()
{
statusLabel->setText(tcpSocket.errorString());
closeConnection();
}
显示错误。
主函数:
intmain(intargc,char*argv[])
{
QApplicationapp(argc,argv);
TripPlannertripPlanner;
tripPlanner.show();
returnapp.exec();
}
[/code]
接下来,我们看看服务器端的实现。
classTripServer:publicQTcpServer
{
Q_OBJECT
public:
TripServer(QObject*parent=0);
private:
voidincomingConnection(intsocketId);
};
服务器端重新实现incomingConnection方法。当客户端尝试连接到服务器的监听端口时,incomingConnection方法被触发。
voidTripServer::incomingConnection(intsocketId)
{
ClientSocket*socket=newClientSocket(this);
socket->setSocketDescriptor(socketId);
}
classClientSocket:publicQTcpSocket
{
Q_OBJECT
public:
ClientSocket(QObject*parent=0);
privateslots:
voidreadClient();
private:
voidgenerateRandomTrip(constQString&from,constQString&to,
constQDate&date,constQTime&time);
quint16nextBlockSize;
};
:QTcpSocket(parent)
{
connect(this,SIGNAL(readyRead()),this,SLOT(readClient()));
connect(this,SIGNAL(disconnected()),this,SLOT(deleteLater()));
nextBlockSize=0;
}
voidClientSocket::readClient()
{
QDataStreamin(this);
in.setVersion(QDataStream::Qt_4_1);
if(nextBlockSize==0){
if(bytesAvailable()<sizeof(quint16))
return;
in>>nextBlockSize;
}
if(bytesAvailable()<nextBlockSize)
return;
quint8requestType;
QStringfrom;
QStringto;
QDatedate;
QTimetime;
quint8flag;
in>>requestType;
if(requestType=='S'){
in>>from>>to>>date>>time>>flag;
srand(from.length()*3600+to.length()*60+time.hour());
intnumTrips=rand()%8;
for(inti=0;i<numTrips;++i)
generateRandomTrip(from,to,date,time);
QDataStreamout(this);
out<<quint16(0xFFFF);
}
close();
}
voidClientSocket::generateRandomTrip(constQString&/*from*/,
constQString&/*to*/,constQDate&date,constQTime&time)
{
QByteArrayblock;
QDataStreamout(&block,QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_1);
quint16duration=rand()%200;
out<<quint16(0)<<date<<time<<duration<<quint8(1)
<<QString("InterCity");
out.device()->seek(0);
out<<quint16(block.size()-sizeof(quint16));
write(block);
}
intmain(intargc,char*argv[])[/code]
{
QApplicationapp(argc,argv);
TripServerserver;
if(!server.listen(QHostAddress::Any,6178)){
cerr<<"Failedtobindtoport"<<endl;
return1;
}
QPushButtonquitButton(QObject::tr("&Quit"));
quitButton.setWindowTitle(QObject::tr("TripServer"));
QObject::connect(&quitButton,SIGNAL(clicked()),
&app,SLOT(quit()));
quitButton.show();
returnapp.exec();
}
相关文章推荐
- 使用QTcpSocket和QTcpServer进行TCP编程
- QT-Socket编程之模拟TCP五层协议解/封装
- 【引用】QT TCP下的socket编程
- 【转】 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架2
- Socket编程实践(6) --TCP服务端注意事项
- Java网络编程(一) TCP/IP,http,socket,长连接,短连接
- C语言的Socket编程例子(TCP和UDP)
- Linux socket编程(TCP,UDP,RAW)
- TCP非阻塞socket编程
- 1.socket编程:socket编程,网络字节序,函数介绍,IP地址转换函数,sockaddr数据结构,网络套接字函数,socket相关函数,TCP server和client
- socket 编程 TCP 实现简单聊天功能
- socket编程—TCP/IP 多客户端
- 基于Socket的TCP和UDP编程
- socket 编程入门教程(一)TCP server 端:5、创建监听嵌套字
- [原]《Java TCP/IP Socket 编程 》读书笔记之十二:各章节要点
- Socket 编程(TCP)
- 基于C#的socket编程的TCP异步实现
- TCP Socket编程 C/C++实现 (Windows Platform SDK)
- [C++] Windows下的socket编程(这是一个简单的TCP/IP例子)
- socket编程(TCP、UDP)