直播推薦
企業(yè)動(dòng)態(tài)
- 紛享銷客發(fā)布首個(gè)企業(yè)級(jí)智能CRM平臺(tái)ShareAI
- 揭秘西企業(yè)數(shù)字化+低碳化轉(zhuǎn)型“工具箱”:西門子Xcelerator
- 企業(yè)AI賦能數(shù)智制造,用友U9 cloud世界級(jí)云ERP煥新升級(jí)
- 《“智“領(lǐng)石化,“質(zhì)“造未來(lái)——威圖石化行業(yè)數(shù)智化實(shí)踐白皮書》隆重發(fā)布
- 攜手共贏!德國(guó)Agfa搭載瑞典IPCO鋼帶,實(shí)現(xiàn)印刷設(shè)備振動(dòng)銳減6倍,提升印刷速度與精度
- 創(chuàng)四方集團(tuán)榮獲“知名商標(biāo)品牌閃亮”證書,助力品牌戰(zhàn)略升級(jí)
- 皇冠CAD(CrownCAD)2025 R3版本來(lái)了,率先開啟C“Ai”D時(shí)代!
- 電費(fèi)砍半!中國(guó)制冷展:海爾發(fā)布AI建筑最新成果
推薦展會(huì)
QTcpSocket 和 QTcpServer類實(shí)現(xiàn)了Qt的Tcp客戶端和服務(wù)器。
tcp是一個(gè)流式協(xié)議。對(duì)于應(yīng)用程序來(lái)說(shuō),數(shù)據(jù)是一個(gè)很長(zhǎng)的流,有點(diǎn)像一個(gè)巨大的文件。
搞成此的協(xié)議建立在面向塊的tcp協(xié)議(Block-oriented)或面向行(Line-oriented )的tcp協(xié)議上。
面向塊的tcp協(xié)議,數(shù)據(jù)被當(dāng)作一個(gè)2進(jìn)制的塊來(lái)傳輸。沒每一個(gè)塊被當(dāng)作一個(gè)定義了大小的,后面跟隨了數(shù)據(jù)的字段。
面向行的tcp協(xié)議,數(shù)據(jù)被當(dāng)作一個(gè)文本文件的一行。一個(gè)傳輸終止于一個(gè)新的行的到來(lái)。
QTcpSocket 繼承自 QIODevice,所以它可以從 QDataStream 或 QTextStream中讀取或?qū)懭霐?shù)據(jù)。
從文件讀數(shù)據(jù)和從網(wǎng)絡(luò)上讀數(shù)據(jù)有一個(gè)明顯的不同點(diǎn): 我們必須保證用“>> ”操作符讀取數(shù)據(jù)時(shí) ,已經(jīng)從另一方接收了足夠的數(shù)據(jù)。如果你這樣做了,那么一個(gè)失敗的結(jié)果是:行為未定義。
我們來(lái)看一個(gè)使用block-oriented tcp協(xié)議的服務(wù)器和客戶端的代碼。
用戶填寫行程的起始地,目的地,日期等,服務(wù)器返回符合要求的行程。
界面用QDesigner設(shè)計(jì)的。叫做“tripplanner.ui”。
請(qǐng)使用uic工具轉(zhuǎn)換。
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 類型,用來(lái)建立一個(gè)tcp連接。
當(dāng)需要提起從服務(wù)器傳遞來(lái)的數(shù)據(jù)塊時(shí),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()));}
構(gòu)造函數(shù)中,我們?cè)O(shè)置時(shí)間控件的默認(rèn)屬性,隱藏progressBar等。 連接tcpSocket的connected(), disconnected(), readyRead(), error(QAbstractSocket::SocketError)信號(hào)到私有的槽。
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;}當(dāng)用戶點(diǎn)擊searchButton時(shí),connectToServer()槽將被執(zhí)行。 它使用tcpSocket.connectToHost建立到
服務(wù)器的連接。connectToServer()槽立即返回。連接的動(dòng)作實(shí)際發(fā)生在這之后。當(dāng)連接建立成功,
QTcpSocket 觸發(fā)connected() 信號(hào)。如果失敗,error()信號(hào)被觸發(fā)。
接著我們?cè)O(shè)置進(jìn)度條以及按鈕的狀態(tài)。
把nextBlockSize設(shè)置為0.表示我們現(xiàn)在并不知道下一個(gè)接收的數(shù)據(jù)塊的大小。
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..."));}當(dāng)connected()信號(hào)被觸發(fā),sendRequest()槽被調(diào)用。sendRequest()相愛難過(guò)服務(wù)器發(fā)送一個(gè)請(qǐng)求(tcpSocket.write(block))。
我們需要在數(shù)據(jù)塊的*個(gè)字段寫入數(shù)據(jù)塊的大小。但是當(dāng)我們些*個(gè)字段時(shí),我們不知道整個(gè)數(shù)據(jù)塊的大小,
所以我們現(xiàn)寫入0(out << quint16(0) ). zui后,當(dāng)數(shù)據(jù)塊填充完畢時(shí),我們計(jì)算數(shù)據(jù)塊的大小,將指針重新
移動(dòng)到QDataStream的開頭(out.device()->seek(0)),重新寫入數(shù)據(jù)塊的大小out << quint16(block.size() - sizeof(quint16))。
zui后,我們發(fā)送數(shù)據(jù)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;}}
當(dāng)QTcpSocket接收到數(shù)據(jù)時(shí),readyRead()信號(hào)被觸發(fā)。updateTableWidget()槽 就被調(diào)用了。
這里我們用了一個(gè)forever循環(huán),這是必須的!因?yàn)槲覀儫o(wú)法保證一次就接到了所有的數(shù)據(jù)塊。可能,我們只接收到數(shù)據(jù)塊的一個(gè)部分,也可能是全部。
forever循環(huán)是如何工作的呢?如果nextBlockSize是0,表示我們沒有獨(dú)到數(shù)據(jù)塊的大小,我們必須重新讀取它。 數(shù)據(jù)塊的大小字段必須至少讀取sizeof(quint16))字節(jié)才能獲得,如果讀取的數(shù)據(jù)少于sizeof(quint16)),必須重新讀取。
如果數(shù)據(jù)塊大小字段為0xFFFF ,表示服務(wù)器端數(shù)據(jù)發(fā)送完畢,我們停止接收。
zui后我們?cè)O(shè)置nextBlockSize 為0,表示下一個(gè)數(shù)據(jù)塊的大小還不知道,我們必須接收。
void TripPlanner::closeConnection(){tcpSocket.close();searchButton->setEnabled(true);stopButton->setEnabled(false);progressBar->hide();}當(dāng)接收到的數(shù)據(jù)塊大小字段的值為0xFFFF,我們關(guān)閉連接。
void TripPlanner::connectionClosedByServer(){if (nextBlockSize != 0xFFFF)statusLabel->setText(tr("Error: Connection closed by server"));closeConnection();}當(dāng)服務(wù)器斷開連接時(shí),如果我們沒有讀到表示數(shù)據(jù)傳送完畢的 0xFFFF,我們發(fā)出一個(gè)錯(cuò)誤。
void TripPlanner::error(){statusLabel->setText(tcpSocket.errorString());closeConnection();}顯示錯(cuò)誤。
主函數(shù):
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按鈕被單擊,我們關(guān)閉連接。
接下來(lái),我們看看服務(wù)器端的實(shí)現(xiàn)。
class TripServer : public QTcpServer{Q_OBJECTpublic:TripServer(QObject *parent = 0);private:void incomingConnection(int socketId);};服務(wù)器端重新實(shí)現(xiàn)incomingConnection方法。當(dāng)客戶端嘗試連接到服務(wù)器的監(jiān)聽端口時(shí),incomingConnection方法被觸發(fā)。
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();}
免責(zé)聲明
- 凡本網(wǎng)注明"來(lái)源:智能制造網(wǎng)"的所有作品,版權(quán)均屬于智能制造網(wǎng),轉(zhuǎn)載請(qǐng)必須注明智能制造網(wǎng),http://www.xashilian.com。違反者本網(wǎng)將追究相關(guān)法律責(zé)任。
- 企業(yè)發(fā)布的公司新聞、技術(shù)文章、資料下載等內(nèi)容,如涉及侵權(quán)、違規(guī)遭投訴的,一律由發(fā)布企業(yè)自行承擔(dān)責(zé)任,本網(wǎng)有權(quán)刪除內(nèi)容并追溯責(zé)任。
- 本網(wǎng)轉(zhuǎn)載并注明自其它來(lái)源的作品,目的在于傳遞更多信息,并不代表本網(wǎng)贊同其觀點(diǎn)或證實(shí)其內(nèi)容的真實(shí)性,不承擔(dān)此類作品侵權(quán)行為的直接責(zé)任及連帶責(zé)任。其他媒體、網(wǎng)站或個(gè)人從本網(wǎng)轉(zhuǎn)載時(shí),必須保留本網(wǎng)注明的作品來(lái)源,并自負(fù)版權(quán)等法律責(zé)任。
- 如涉及作品內(nèi)容、版權(quán)等問(wèn)題,請(qǐng)?jiān)谧髌钒l(fā)表之日起一周內(nèi)與本網(wǎng)聯(lián)系,否則視為放棄相關(guān)權(quán)利。
2025第十一屆中國(guó)國(guó)際機(jī)電產(chǎn)品交易會(huì) 暨先進(jìn)制造業(yè)博覽會(huì)
展會(huì)城市:合肥市展會(huì)時(shí)間:2025-09-20