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();}
上一篇:彈性式壓力檢測元件膜片
免責聲明
- 凡本網注明"來源:智能制造網"的所有作品,版權均屬于智能制造網,轉載請必須注明智能制造網,http://www.xksjj.com。違反者本網將追究相關法律責任。
- 企業發布的公司新聞、技術文章、資料下載等內容,如涉及侵權、違規遭投訴的,一律由發布企業自行承擔責任,本網有權刪除內容并追溯責任。
- 本網轉載并注明自其它來源的作品,目的在于傳遞更多信息,并不代表本網贊同其觀點或證實其內容的真實性,不承擔此類作品侵權行為的直接責任及連帶責任。其他媒體、網站或個人從本網轉載時,必須保留本網注明的作品來源,并自負版權等法律責任。
- 如涉及作品內容、版權等問題,請在作品發表之日起一周內與本網聯系,否則視為放棄相關權利。
2025成都國際無人系統(機)技術及設備展覽會
展會城市:成都市展會時間:2025-10-10