您现在的位置:智能制造网>技术首页>技术交流

QT TCP下的socket编程

2013年11月07日 19:34来源:上海安嵌信息科技有限公司 >>进入该公司展台人气:1469

QTcpSocket 和 QTcpServer类实现了Qt的Tcp客户端和服务器。
tcp是一个流式协议。对于应用程序来说,数据是一个很长的流,有点像一个巨大的文件。
搞成此的协议建立在面向块的tcp协议(Block-oriented)或面向行(Line-oriented )的tcp协议上。

面向块的tcp协议,数据被当作一个2进制的块来传输。没每一个块被当作一个定义了大小的,后面跟随了数据的字段。
面向行的tcp协议,数据被当作一个文本文件的一行。一个传输终止于一个新的行的到来。

QTcpSocket 继承自 QIODevice,所以它可以从 QDataStream 或 QTextStream中读取或写入数据。

从文件读数据和从网络上读数据有一个明显的不同点: 我们必须保证用“>> ”操作符读取数据时 ,已经从另一方接收了足够的数据。如果你这样做了,那么一个失败的结果是:行为未定义。

我们来看一个使用block-oriented tcp协议的服务器和客户端的代码。

 

用户填写行程的起始地,目的地,日期等,服务器返回符合要求的行程。

界面用QDesigner设计的。叫做“tripplanner.ui”。

请使用uic工具转换。

include "ui_tripplanner.h"
class TripPlanner : public QDialog, public Ui::TripPlanner
{
    Q_OBJECT
public:
    TripPlanner(QWidget *parent = 0);
private slots:
    void connectToServer();
    void sendRequest();
    void updateTableWidget();
    void stopSearch();
    void connectionClosedByServer();
    void error();
private:
    void closeConnection();
    QTcpSocket tcpSocket;
    quint16 nextBlockSize;
};

tcpSocket变量是QTcpSocket 类型,用来建立一个tcp连接。

当需要提起从服务器传递来的数据块时,nextBlockSize将被使用。

TripPlanner::TripPlanner(QWidget *parent): QDialog(parent){setupUi(this);QDateTime dateTime = 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)信号到私有的槽。

void TripPlanner::connectToServer(){tcpSocket.connectToHost("tripserver.zugbahn.de", 6178);tableWidget->setRowCount(0);searchButton->setEnabled(false);stopButton->setEnabled(true);statusLabel->setText(tr("Connecting to server..."));progressBar->show();nextBlockSize = 0;}当用户点击searchButton时,connectToServer()槽将被执行。 它使用tcpSocket.connectToHost建立到 
服务器的连接。connectToServer()槽立即返回。连接的动作实际发生在这之后。当连接建立成功, 
QTcpSocket 触发connected() 信号。如果失败,error()信号被触发。 
接着我们设置进度条以及按钮的状态。 
把nextBlockSize设置为0.表示我们现在并不知道下一个接收的数据块的大小。 
   
void TripPlanner::sendRequest(){QByteArray block;QDataStream out(&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("Sending request..."));}当connected()信号被触发,sendRequest()槽被调用。sendRequest()相爱难过服务器发送一个请求(tcpSocket.write(block))。 
  
   
  
我们需要在数据块的*个字段写入数据块的大小。但是当我们些*个字段时,我们不知道整个数据块的大小, 
  
所以我们现写入0(out << quint16(0) ). zui后,当数据块填充完毕时,我们计算数据块的大小,将指针重新 
  
移动到QDataStream的开头(out.device()->seek(0)),重新写入数据块的大小out << quint16(block.size() - sizeof(quint16))。 
  
zui后,我们发送数据tcpSocket.write(block)。 
  
void TripPlanner::updateTableWidget(){QDataStream in(&tcpSocket);in.setVersion(QDataStream::Qt_4_1);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)<< trainType;for (int i = 0; i < fields.count(); ++i)tableWidget->setItem(row, i,new QTableWidgetItem(fields[i]));nextBlockSize = 0;}} 
  
当QTcpSocket接收到数据时,readyRead()信号被触发。updateTableWidget()槽 就被调用了。 
  
这里我们用了一个forever循环,这是必须的!因为我们无法保证一次就接到了所有的数据块。可能,我们只接收到数据块的一个部分,也可能是全部。 
  
  
   
  
forever循环是如何工作的呢?如果nextBlockSize是0,表示我们没有独到数据块的大小,我们必须重新读取它。 数据块的大小字段必须至少读取sizeof(quint16))字节才能获得,如果读取的数据少于sizeof(quint16)),必须重新读取。 
  
如果数据块大小字段为0xFFFF ,表示服务器端数据发送完毕,我们停止接收。 
  
   
  
zui后我们设置nextBlockSize 为0,表示下一个数据块的大小还不知道,我们必须接收。 
  
   
  
void TripPlanner::closeConnection(){tcpSocket.close();searchButton->setEnabled(true);stopButton->setEnabled(false);progressBar->hide();}当接收到的数据块大小字段的值为0xFFFF,我们关闭连接。 
  
   
  
  
   
  
void TripPlanner::connectionClosedByServer(){if (nextBlockSize != 0xFFFF)statusLabel->setText(tr("Error: Connection closed by server"));closeConnection();}当服务器断开连接时,如果我们没有读到表示数据传送完毕的 0xFFFF,我们发出一个错误。 
  
   
  
void TripPlanner::error(){statusLabel->setText(tcpSocket.errorString());closeConnection();}显示错误。 
  
   
  
主函数: 
  
int main(int argc, char *argv[]){QApplication app(argc, argv);TripPlanner tripPlanner;tripPlanner.show();return app.exec();} 
  
void TripPlanner::stopSearch(){statusLabel->setText(tr("Search stopped"));closeConnection();} 
 如果stopServer按钮被单击,我们关闭连接。
   
接下来,我们看看服务器端的实现。 
class TripServer : public QTcpServer{Q_OBJECTpublic:TripServer(QObject *parent = 0);private:void incomingConnection(int socketId);};服务器端重新实现incomingConnection方法。当客户端尝试连接到服务器的监听端口时,incomingConnection方法被触发。 
void TripServer::incomingConnection(int socketId){ClientSocket *socket = new ClientSocket(this);socket->setSocketDescriptor(socketId);} 
  
   
  
   
  
   
  
   
  
class ClientSocket : public QTcpSocket{Q_OBJECTpublic:ClientSocket(QObject *parent = 0);private slots:void readClient();private:void generateRandomTrip(const QString &from, const QString &to,const QDate &date, const QTime &time);quint16 nextBlockSize;}; 
  
  
ClientSocket::ClientSocket(QObject *parent): QTcpSocket(parent){connect(this, SIGNAL(readyRead()), this, SLOT(readClient()));connect(this, SIGNAL(disconnected()), this, SLOT(deleater()));nextBlockSize = 0;} 
  
   
  
void ClientSocket::readClient(){QDataStream in(this);in.setVersion(QDataStream::Qt_4_1);if (nextBlockSize == 0) {if (bytesAvailable() < sizeof(quint16))return;in >> nextBlockSize;}if (bytesAvailable() < nextBlockSize)return;quint8 requestType;QString from;QString to;QDate date;QTime time;quint8 flag;in >> requestType;if (requestType == 'S') {in >> from >> to >> date >> time >> flag;srand(from.length() * 3600 + to.length() * 60 + time.hour());int numTrips = rand() % 8;for (int i = 0; i < numTrips; ++i)generateRandomTrip(from, to, date, time);QDataStream out(this);out << quint16(0xFFFF);}close();} 
  
void ClientSocket::generateRandomTrip(const QString & ,const QString & , const QDate &date, const QTime &time){QByteArray block;QDataStream out(&block, QIODevice::WriteOnly);out.setVersion(QDataStream::Qt_4_1);quint16 duration = rand() % 200;out << quint16(0) << date << time << duration << quint8(1)<< QString("InterCity");out.device()->seek(0);out << quint16(block.size() - sizeof(quint16));write(block);} 
  
  
int main(int argc, char *argv[]){QApplication app(argc, argv);TripServer server;if (!server.listen(QHostAddress::Any, 6178)) {cerr << "Failed to bind to port" << endl;return 1;}QPushButton quitButton(QObject::tr("&Quit"));quitButton.setWindowTitle(QObject::tr("Trip Server"));QObject::connect(&quitButton, SIGNAL(clicked()),&app, SLOT(quit()));quitButton.show();return app.exec();} 
全年征稿/资讯合作 联系邮箱:1271141964@qq.com
  • 凡本网注明"来源:智能制造网"的所有作品,版权均属于智能制造网,转载请必须注明智能制造网,https://www.gkzhan.com。违反者本网将追究相关法律责任。
  • 企业发布的公司新闻、技术文章、资料下载等内容,如涉及侵权、违规遭投诉的,一律由发布企业自行承担责任,本网有权删除内容并追溯责任。
  • 本网转载并注明自其它来源的作品,目的在于传递更多信息,并不代表本网赞同其观点或证实其内容的真实性,不承担此类作品侵权行为的直接责任及连带责任。其他媒体、网站或个人从本网转载时,必须保留本网注明的作品来源,并自负版权等法律责任。
  • 如涉及作品内容、版权等问题,请在作品发表之日起一周内与本网联系,否则视为放弃相关权利。


编辑精选

更多


宣传样本推荐图书

旗下子站

工控网机器人仪器仪表物联网3D打印工业软件金属加工机械包装机械印刷机械农业机械食品加工设备制药设备仓储物流环保设备造纸机械工程机械纺织机械化工设备电子加工设备水泥设备海洋水利装备矿冶设备新能源设备服装机械印染机械制鞋机械玻璃机械陶瓷设备橡塑设备船舶设备电子元器件电气设备



关于我们|本站服务|会员服务|企业建站|旗下网站|友情链接| 兴旺通| 产品分类浏览|产品sitemap

智能制造网 - 工业4.0时代智能制造领域“互联网+”服务平台

Copyright gkzhan.comAll Rights Reserved法律顾问:浙江天册律师事务所 贾熙明律师

客服热线:0571-87756395加盟热线:0571-87759904媒体合作:0571-89719789

客服部:编辑部:展会合作:市场部:

关闭