C++ Qt開發(fā)之使用QTcpSocket實現(xiàn)TCP網(wǎng)絡(luò)通信
前言
Qt 是一個跨平臺C++圖形界面開發(fā)庫,利用Qt可以快速開發(fā)跨平臺窗體應(yīng)用程序,在Qt中我們可以通過拖拽的方式將不同組件放到指定的位置,實現(xiàn)圖形化開發(fā)極大的方便了開發(fā)效率,本章將重點介紹如何運用QTcpSocket
組件實現(xiàn)基于TCP的網(wǎng)絡(luò)通信功能。
QTcpSocket
和QTcpServer
是Qt中用于實現(xiàn)基于TCP(Transmission Control Protocol)通信的兩個關(guān)鍵類。TCP是一種面向連接的協(xié)議,它提供可靠的、雙向的、面向字節(jié)流的通信。這兩個類允許Qt應(yīng)用程序在網(wǎng)絡(luò)上建立客戶端和服務(wù)器之間的連接。
以下是QTcpSocket
類的一些常用函數(shù):
函數(shù) | 描述 |
---|---|
QTcpSocket() | 構(gòu)造函數(shù),創(chuàng)建一個新的QTcpSocket對象。 |
~QTcpSocket() | 析構(gòu)函數(shù),釋放QTcpSocket對象及其資源。 |
void connectToHost(const QString &hostName, quint16 port) | 嘗試與指定主機名和端口建立連接。 |
void disconnectFromHost() | 斷開與主機的連接。 |
QAbstractSocket::SocketState state() const | 返回套接字的當(dāng)前狀態(tài)。 |
QHostAddress peerAddress() const | 返回與套接字連接的遠程主機的地址。 |
quint16 peerPort() const | 返回與套接字連接的遠程主機的端口。 |
QAbstractSocket::SocketError error() const | 返回套接字的當(dāng)前錯誤代碼。 |
qint64 write(const char *data, qint64 maxSize) | 將數(shù)據(jù)寫入套接字,返回實際寫入的字節(jié)數(shù)。 |
qint64 read(char *data, qint64 maxSize) | 從套接字讀取數(shù)據(jù),返回實際讀取的字節(jié)數(shù)。 |
void readyRead() | 當(dāng)套接字有可供讀取的新數(shù)據(jù)時發(fā)出信號。 |
void bytesWritten(qint64 bytes) | 當(dāng)套接字已經(jīng)寫入指定字節(jié)數(shù)的數(shù)據(jù)時發(fā)出信號。 |
void error(QAbstractSocket::SocketError socketError) | 當(dāng)套接字發(fā)生錯誤時發(fā)出信號。 |
以下是QTcpServer
類的一些常用函數(shù)及其簡要解釋:
函數(shù) | 描述 |
---|---|
QTcpServer() | 構(gòu)造函數(shù),創(chuàng)建一個新的QTcpServer對象。 |
~QTcpServer() | 析構(gòu)函數(shù),釋放QTcpServer對象及其資源。 |
bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0) | 開始監(jiān)聽指定的地址和端口。 |
void close() | 停止監(jiān)聽并關(guān)閉服務(wù)器。 |
bool isListening() const | 返回服務(wù)器是否正在監(jiān)聽連接。 |
QList<QTcpSocket*> pendingConnections() | 返回等待處理的掛起連接的列表。 |
virtual void incomingConnection(qintptr socketDescriptor) | 當(dāng)有新連接時調(diào)用,可以在子類中實現(xiàn)以處理新連接。 |
void maxPendingConnections() const | 返回允許的最大掛起連接數(shù)。 |
void setMaxPendingConnections(int numConnections) | 設(shè)置允許的最大掛起連接數(shù)。 |
QNetworkProxy proxy() const | 返回服務(wù)器的代理設(shè)置。 |
void setProxy(const QNetworkProxy &networkProxy) | 設(shè)置服務(wù)器的代理設(shè)置。 |
QAbstractSocket::SocketError serverError() const | 返回服務(wù)器的當(dāng)前錯誤代碼。 |
QString errorString() const | 返回服務(wù)器的錯誤消息字符串。 |
void pauseAccepting() | 暫停接受新連接,但保持現(xiàn)有連接。 |
void resumeAccepting() | 恢復(fù)接受新連接。 |
void close() | 關(guān)閉服務(wù)器。 |
如上這些只是常用函數(shù)的簡要描述,詳細的函數(shù)說明和用法可以參考Qt官方文檔或相關(guān)文檔。
1.通信的流程
1.1服務(wù)端流程
在使用TCP通信時同樣需要導(dǎo)入Qt+=network
模塊,并在頭文件中引入QTcpServer
和QTcpSocket
兩個模塊,當(dāng)有了模塊的支持,接著就是偵聽套接字,此處可通過調(diào)用server.listen
來實現(xiàn)偵聽,此函數(shù)原型如下;
bool QTcpServer::listen( const QHostAddress &address = QHostAddress::Any, quint16 port = 0 );
這個函數(shù)用于開始在指定的地址和端口上監(jiān)聽連接。它的參數(shù)包括:
address
:一個QHostAddress
對象,指定要監(jiān)聽的主機地址。默認為QHostAddress::Any
,表示監(jiān)聽所有可用的網(wǎng)絡(luò)接口。port
:一個quint16
類型的端口號,指定要監(jiān)聽的端口。如果設(shè)置為0,系統(tǒng)將選擇一個可用的未使用端口。
函數(shù)返回一個bool
值,表示是否成功開始監(jiān)聽。如果成功返回true
,否則返回false
,并且可以通過調(diào)用errorString()
獲取錯誤消息。
緊隨套接字偵聽其后,通過使用一個waitForNewConnection
等待新的連接到達。它的原型如下:
bool QTcpServer::waitForNewConnection( int msec = 0, bool *timedOut = nullptr );
該函數(shù)在服務(wù)器接受新連接之前會一直阻塞。參數(shù)包括:
msec
:等待連接的超時時間(以毫秒為單位)。如果設(shè)置為0(默認值),則表示無限期等待,直到有新連接到達。timedOut
:一個可選的布爾指針,用于指示等待是否超時。如果傳遞了此參數(shù),并且等待時間達到了指定的超時時間,*timedOut
將被設(shè)置為true
,否則為false
。如果不關(guān)心超時,可以將此參數(shù)設(shè)置為nullptr
。
函數(shù)返回一個布爾值,表示是否成功等待新連接。如果在超時時間內(nèi)有新連接到達,返回true
,否則返回false
。如果等待超時,可以通過檢查timedOut
參數(shù)來確定。如果函數(shù)返回false
,可以通過調(diào)用errorString()
獲取錯誤消息。
套接字的接收會使用nextPendingConnection()
函數(shù)來實現(xiàn),nextPendingConnection
是 QTcpServer
類的成員函數(shù),用于獲取下一個已接受的連接的套接字(QTcpSocket
)。它的原型如下:
QTcpSocket *QTcpServer::nextPendingConnection();
函數(shù)返回一個指向新連接套接字的指針。如果沒有已接受的連接,則返回 nullptr
。
使用這個函數(shù),你可以在服務(wù)器接受連接之后獲取相應(yīng)的套接字,以便進行數(shù)據(jù)傳輸和通信。一般來說,在收到 newConnection
信號后,你可以調(diào)用這個函數(shù)來獲取新連接的套接字。
當(dāng)有了套接字以后,就可以通過QTcpServer
指針判斷對應(yīng)的套接字狀態(tài),一般套接字的狀態(tài)被定義在QAbstractSocket
類內(nèi)。以下是QAbstractSocket
類中定義的一些狀態(tài)及其對應(yīng)的標志:
狀態(tài)標志 | 描述 |
---|---|
UnconnectedState | 未連接狀態(tài),套接字沒有連接到遠程主機。 |
HostLookupState | 正在查找主機地址狀態(tài),套接字正在解析主機名。 |
ConnectingState | 連接中狀態(tài),套接字正在嘗試與遠程主機建立連接。 |
ConnectedState | 已連接狀態(tài),套接字已經(jīng)成功連接到遠程主機。 |
BoundState | 已綁定狀態(tài),套接字已經(jīng)與地址和端口綁定。 |
ClosingState | 關(guān)閉中狀態(tài),套接字正在關(guān)閉連接。 |
ListeningState | 監(jiān)聽中狀態(tài),用于QTcpServer,表示服務(wù)器正在監(jiān)聽連接。 |
這些狀態(tài)反映了套接字在不同階段的連接和通信狀態(tài)。在實際使用中,可以通過調(diào)用state()
函數(shù)獲取當(dāng)前套接字的狀態(tài),并根據(jù)需要處理相應(yīng)的狀態(tài)。例如,可以使用信號和槽機制來捕獲狀態(tài)變化,以便在連接建立或斷開時執(zhí)行相應(yīng)的操作。
當(dāng)套接字被連接后則可以通過socket->write()
方法向上線客戶端發(fā)送一個字符串,此處我們以發(fā)送lyshark
為例,發(fā)送時需要向write()
中傳入兩個參數(shù)。其原型如下:
qint64 QTcpSocket::write(const char *data, qint64 maxSize);
該函數(shù)接受兩個參數(shù):
data
:指向要寫入套接字的數(shù)據(jù)的指針。maxSize
:要寫入的數(shù)據(jù)的最大字節(jié)數(shù)。
函數(shù)返回實際寫入的字節(jié)數(shù),如果發(fā)生錯誤,則返回 -1。在寫入數(shù)據(jù)之后,可以使用 bytesWritten
信號來獲取寫入的字節(jié)數(shù)。此外,你也可以使用 waitForBytesWritten
函數(shù)來阻塞等待直到所有數(shù)據(jù)都被寫入。
至此服務(wù)端代碼可總結(jié)為如下案例;
#include <QCoreApplication> #include <QTcpServer> #include <QTcpSocket> #include <iostream> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QTcpServer server; server.listen(QHostAddress::Any,9000); server.waitForNewConnection(100000); QTcpSocket *socket; socket = server.nextPendingConnection(); if(socket->state() && QAbstractSocket::ConnectedState) { QByteArray bytes = QString("lyshark").toUtf8(); socket->write(bytes.data(),bytes.length()); } socket->close(); server.close(); return a.exec(); }
1.2客戶端流程
客戶端的流程與服務(wù)端基本保持一致,唯一的區(qū)別在于將server.listen
更換為socket.connectToHost
連接到對應(yīng)的主機,QTcpSocket
的 connectToHost
函數(shù)的原型如下:
void QTcpSocket::connectToHost( const QString &hostName, quint16 port, OpenMode openMode = ReadWrite );
hostName
:遠程主機的主機名或IP地址。port
:要連接的端口號。openMode
:套接字的打開模式,默認為ReadWrite
。
函數(shù)用于初始化與指定遠程主機和端口的連接。在實際使用中,你可以通過調(diào)用這個函數(shù)來發(fā)起與目標主機的連接嘗試。
讀取數(shù)據(jù)時可以使用readAll
函數(shù)來實現(xiàn),socket.readAll()
是 QTcpSocket
類的成員函數(shù),用于讀取所有可用的數(shù)據(jù)并返回一個 QByteArray
對象。其函數(shù)函數(shù)原型如下:
QByteArray QTcpSocket::readAll();
該函數(shù)返回一個包含從套接字中讀取的所有數(shù)據(jù)的 QByteArray
對象。通常,你可以通過這個函數(shù)來獲取已經(jīng)到達的所有數(shù)據(jù),然后對這些數(shù)據(jù)進行進一步的處理。其客戶端功能如下所示;
#include <QCoreApplication> #include <QTcpServer> #include <QTcpSocket> #include <iostream> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QTcpSocket socket; socket.connectToHost(QHostAddress::LocalHost,9000); if(socket.state() && QAbstractSocket::ConnectedState) { socket.waitForReadyRead(10000); QByteArray ref = socket.readAll(); QString ref_string; ref_string.prepend(ref); std::cout << ref_string.toStdString() << std::endl; } socket.close(); return a.exec(); }
2.圖形化應(yīng)用
2.1 服務(wù)端流程
與命令行版本的網(wǎng)絡(luò)通信不同,圖形化部分需要使用信號與槽函數(shù)進行綁定,所有的通信流程都是基于信號的,對于服務(wù)端而言我們需要導(dǎo)入QTcpServer
、QtNetwork
、QTcpSocket
模塊,并新增四個槽函數(shù)分別對應(yīng)四個信號;
信號 | 槽函數(shù) | 描述 |
---|---|---|
connected() | onClientConnected() | 當(dāng) tcpSocket 成功連接到遠程主機時觸發(fā),執(zhí)行 onClientConnected() 函數(shù)。 |
disconnected() | onClientDisconnected() | 當(dāng) tcpSocket 斷開連接時觸發(fā),執(zhí)行 onClientDisconnected() 函數(shù)。 |
stateChanged(QAbstractSocket::SocketState) | onSocketStateChange(QAbstractSocket::SocketState) | 當(dāng) tcpSocket 的狀態(tài)發(fā)生變化時觸發(fā),執(zhí)行 onSocketStateChange() 函數(shù),傳遞新的狀態(tài)。 |
readyRead() | onSocketReadyRead() | 當(dāng) tcpSocket 有可讀取的新數(shù)據(jù)時觸發(fā),執(zhí)行 onSocketReadyRead() 函數(shù)。 |
在程序入口處我們通過new QTcpServer(this)
新建TCP套接字類,并通過connect()
連接到初始化槽函數(shù)上,當(dāng)程序運行后會首先觸發(fā)newConnection
信號,執(zhí)行onNewConnection
槽函數(shù)。
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); // 新建TCP套接字類 tcpServer=new QTcpServer(this); // 連接信號初始化其他信號 connect(tcpServer,SIGNAL(newConnection()),this,SLOT(onNewConnection())); }
而在槽函數(shù)onNewConnection
中,通過nextPendingConnection
新建一個套接字,并綁定其他四個槽函數(shù),這里的槽函數(shù)功能各不相同,將其對應(yīng)的信號綁定到對應(yīng)槽函數(shù)上即可;
// 初始化信號槽函數(shù) void MainWindow::onNewConnection() { // 創(chuàng)建新套接字 tcpSocket = tcpServer->nextPendingConnection(); // 連接觸發(fā)信號 connect(tcpSocket, SIGNAL(connected()),this, SLOT(onClientConnected())); onClientConnected(); // 關(guān)閉觸發(fā)信號 connect(tcpSocket, SIGNAL(disconnected()),this, SLOT(onClientDisconnected())); // 狀態(tài)改變觸發(fā)信號 connect(tcpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(onSocketStateChange(QAbstractSocket::SocketState))); onSocketStateChange(tcpSocket->state()); // 讀入數(shù)據(jù)觸發(fā)信號 connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(onSocketReadyRead())); }
當(dāng)讀者點擊偵聽時則直接調(diào)用tcpServer->listen
實現(xiàn)對本地IP的8888
端口的偵聽功能,停止偵聽則是調(diào)用tcpServer->close
函數(shù)實現(xiàn),如下所示;
// 開始偵聽 void MainWindow::on_pushButton_2_clicked() { // 此處指定綁定本機的8888端口 tcpServer->listen(QHostAddress::LocalHost,8888); ui->plainTextEdit->appendPlainText("[+] 開始監(jiān)聽"); ui->plainTextEdit->appendPlainText(" 服務(wù)器地址:" + tcpServer->serverAddress().toString() + " 服務(wù)器端口:"+QString::number(tcpServer->serverPort()) ); } // 停止偵聽 void MainWindow::on_pushButton_3_clicked() { if (tcpServer->isListening()) { tcpServer->close(); } }
對于讀取數(shù)據(jù)可以通過canReadLine()
函數(shù)判斷行,并通過tcpClient->readLine()
逐行讀入數(shù)據(jù),相對應(yīng)的發(fā)送數(shù)據(jù)可通過調(diào)用tcpSocket->write
函數(shù)實現(xiàn),在發(fā)送之前需要將其轉(zhuǎn)換為QByteArray
類型的字符串格式,如下所示;
// 讀取數(shù)據(jù) void MainWindow::onSocketReadyRead() { while(tcpSocket->canReadLine()) ui->plainTextEdit->appendPlainText("[接收] | " + tcpSocket->readLine()); } // 發(fā)送數(shù)據(jù) void MainWindow::on_pushButton_clicked() { QString msg=ui->lineEdit->text(); ui->plainTextEdit->appendPlainText("[發(fā)送] | " + msg); QByteArray str=msg.toUtf8(); str.append('\n'); tcpSocket->write(str); }
運行服務(wù)端程序,并點擊偵聽按鈕此時將會在本地的8888
端口上啟用偵聽,如下圖所示;
2.2客戶端流程
對于客戶端而言同樣需要綁定四個信號并對應(yīng)到特定的槽函數(shù)上,其初始化部分與服務(wù)端保持一致,唯一不同的是客戶端使用connectToHost
函數(shù)鏈接到服務(wù)端上,斷開連接時使用的是disconnectFromHost
函數(shù),如下所示;
// 連接服務(wù)器時觸發(fā) void MainWindow::on_pushButton_2_clicked() { // 連接到8888端口 tcpClient->connectToHost(QHostAddress::LocalHost,8888); } // 斷開時觸發(fā) void MainWindow::on_pushButton_3_clicked() { if (tcpClient->state()==QAbstractSocket::ConnectedState) tcpClient->disconnectFromHost(); }
此處的讀取數(shù)據(jù)與服務(wù)端保持一致,發(fā)送數(shù)據(jù)時則是通過tcpClient->write(str)
函數(shù)直接傳遞給客戶端,代碼如下所示;
// 讀取數(shù)據(jù)時觸發(fā) void MainWindow::onSocketReadyRead() { while(tcpClient->canReadLine()) { ui->plainTextEdit->appendPlainText("[接收] | " + tcpClient->readLine()); } } // 發(fā)送消息時觸發(fā) void MainWindow::on_pushButton_clicked() { QString msg=ui->lineEdit->text(); ui->plainTextEdit->appendPlainText("[發(fā)送] | " + msg); QByteArray str=msg.toUtf8(); str.append('\n'); tcpClient->write(str); }
運行后,服務(wù)端啟用偵聽等待客戶端連接,客戶端連接后,雙方則可以實現(xiàn)數(shù)據(jù)的收發(fā)功能,由于采用了信號機制,兩者的收發(fā)并不會阻斷可同時進行,如下圖所示;
到此這篇關(guān)于C++ Qt開發(fā)之使用QTcpSocket實現(xiàn)TCP網(wǎng)絡(luò)通信的文章就介紹到這了,更多相關(guān)Qt QTcpSocket TCP網(wǎng)絡(luò)通信內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實現(xiàn)學(xué)生宿舍管理系統(tǒng)
這篇文章主要為大家詳細介紹了C++實現(xiàn)學(xué)生宿舍管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03深入了解C語言結(jié)構(gòu)化的程序設(shè)計
這篇文章主要介紹了C語言編程中程序的一些基本的編寫優(yōu)化技巧,文中涉及到了基礎(chǔ)的C程序內(nèi)存方面的知識,非常推薦!需要的朋友可以參考下2021-07-07kernel利用pt?regs劫持seq?operations的遷移過程詳解
這篇文章主要為大家介紹了kernel利用pt_regs劫持seq_operations進行遷移的過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-05-05