Qt中TCP Socket的實(shí)現(xiàn)
1 -> 概述
TCP(Transmission Control Protocol,傳輸控制協(xié)議)是一種面向連接、可靠、基于字節(jié)流的傳輸層通信協(xié)議。在 Qt 框架中,TCP 網(wǎng)絡(luò)編程主要通過(guò) QTcpServer 和 QTcpSocket 兩個(gè)核心類來(lái)實(shí)現(xiàn),它們封裝了底層 Socket 的復(fù)雜性,提供了面向?qū)ο?、事件?qū)動(dòng)的高級(jí) API,大大簡(jiǎn)化了網(wǎng)絡(luò)應(yīng)用的開(kāi)發(fā)過(guò)程。
Qt 的網(wǎng)絡(luò)模塊是跨平臺(tái)的,這意味著使用 Qt 編寫(xiě)的 TCP 程序可以在 Windows、Linux、macOS 等操作系統(tǒng)上運(yùn)行而無(wú)需修改代碼,這對(duì)于需要部署在多種環(huán)境下的應(yīng)用程序來(lái)說(shuō)是一個(gè)巨大的優(yōu)勢(shì)。
在 Qt 中進(jìn)行 TCP 編程,本質(zhì)上是在構(gòu)建客戶端-服務(wù)器模型。服務(wù)器負(fù)責(zé)監(jiān)聽(tīng)指定端口、接受客戶端連接請(qǐng)求并與客戶端進(jìn)行數(shù)據(jù)交換;客戶端則主動(dòng)發(fā)起連接,與服務(wù)器建立通信鏈路并進(jìn)行數(shù)據(jù)傳輸。Qt 的信號(hào)與槽機(jī)制非常適合處理網(wǎng)絡(luò)事件,例如連接建立、數(shù)據(jù)到達(dá)、連接斷開(kāi)等,使得程序邏輯清晰、易于維護(hù)。
2 -> 核心 API 詳解
2.1 -> QTcpServer
QTcpServer 類用于創(chuàng)建一個(gè) TCP 服務(wù)器,其主要職責(zé)是監(jiān)聽(tīng)指定 IP 地址和端口,接受傳入的連接請(qǐng)求,并為每個(gè)連接創(chuàng)建一個(gè)獨(dú)立的 QTcpSocket 對(duì)象來(lái)處理后續(xù)通信。
2.1.1 -> 關(guān)鍵方法
bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)
啟動(dòng)服務(wù)器監(jiān)聽(tīng)。- address:監(jiān)聽(tīng)的 IP 地址,QHostAddress::Any 表示監(jiān)聽(tīng)所有網(wǎng)絡(luò)接口。
- port:端口號(hào),如果設(shè)為 0,系統(tǒng)會(huì)自動(dòng)分配一個(gè)可用端口。
- 返回值:成功監(jiān)聽(tīng)返回 true,否則返回 false(可通過(guò) errorString() 獲取錯(cuò)誤信息)。
QTcpSocket *nextPendingConnection()
獲取下一個(gè)等待處理的客戶端連接。
該方法返回一個(gè)已建立連接的 QTcpSocket 對(duì)象,該對(duì)象由 QTcpServer 內(nèi)部創(chuàng)建并管理,用于后續(xù)與對(duì)應(yīng)客戶端的數(shù)據(jù)交換。
注意:每次調(diào)用都應(yīng)檢查返回值是否為 nullptr。
2.1.2 -> 關(guān)鍵信號(hào)
- void newConnection()
當(dāng)有新的客戶端連接成功時(shí)觸發(fā)此信號(hào)。通常在此信號(hào)的槽函數(shù)中調(diào)用 nextPendingConnection() 獲取新連接并進(jìn)行初始化設(shè)置(如連接信號(hào)槽、保存 socket 對(duì)象等)。
2.1.3 -> 工作流程簡(jiǎn)述
- 創(chuàng)建 QTcpServer 對(duì)象。
- 調(diào)用 listen() 綁定端口并開(kāi)始監(jiān)聽(tīng)。
- 連接 newConnection() 信號(hào)到自定義槽函數(shù)。
- 在槽函數(shù)中通過(guò) nextPendingConnection() 獲取客戶端 socket。
- 為獲取到的 socket 連接相關(guān)信號(hào)(如 readyRead、disconnected),實(shí)現(xiàn)業(yè)務(wù)邏輯。
2.2 -> QTcpSocket
QTcpSocket 類代表一個(gè) TCP 連接,既可用于客戶端發(fā)起連接,也可用于服務(wù)器端處理某個(gè)具體客戶端的通信。它繼承自 QAbstractSocket,并間接繼承自 QIODevice,因此可以像文件一樣進(jìn)行讀寫(xiě)操作。
2.2.1 -> 關(guān)鍵方法
void connectToHost(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite)
(客戶端使用)連接到指定的服務(wù)器。- hostName:主機(jī)名或 IP 地址。
- port:目標(biāo)端口。
- openMode:打開(kāi)模式,通常為 ReadWrite。
qint64 read(char *data, qint64 maxSize) / QByteArray readAll() / QByteArray read(qint64 maxSize)
從接收緩沖區(qū)讀取數(shù)據(jù)。readAll() 會(huì)讀取所有當(dāng)前可用的數(shù)據(jù),返回 QByteArray 對(duì)象。qint64 write(const char *data, qint64 size) / qint64 write(const QByteArray &data)
向連接中寫(xiě)入數(shù)據(jù)。數(shù)據(jù)會(huì)被放入發(fā)送緩沖區(qū),由 Qt 在后臺(tái)異步發(fā)送。void disconnectFromHost()
主動(dòng)斷開(kāi)連接。斷開(kāi)過(guò)程是異步的,可配合 disconnected() 信號(hào)使用。void abort()
立即中止連接,丟棄所有未發(fā)送或未接收的數(shù)據(jù)。
2.2.2 -> 關(guān)鍵信號(hào)
void connected()
成功連接到遠(yuǎn)程主機(jī)后觸發(fā)。void disconnected()
連接斷開(kāi)時(shí)觸發(fā)(可能是遠(yuǎn)程主機(jī)關(guān)閉、網(wǎng)絡(luò)故障或主動(dòng)斷開(kāi))。void readyRead()
當(dāng)有新的數(shù)據(jù)到達(dá)并可供讀取時(shí)觸發(fā)。由于 TCP 是流式協(xié)議,數(shù)據(jù)可能分多次到達(dá),通常在此信號(hào)的槽函數(shù)中循環(huán)讀取直到 bytesAvailable() 為 0。void bytesWritten(qint64 bytes)
當(dāng)部分?jǐn)?shù)據(jù)被成功寫(xiě)入底層 socket 時(shí)觸發(fā),可用于實(shí)現(xiàn)發(fā)送進(jìn)度指示或流量控制。
2.2.3 -> 關(guān)鍵屬性與狀態(tài)
state()
返回 socket 的當(dāng)前狀態(tài)(如 UnconnectedState、ConnectedState 等)。error() 與 errorString()
獲取最后一次發(fā)生的錯(cuò)誤代碼及描述。peerAddress() 與 peerPort()
獲取遠(yuǎn)程對(duì)端的 IP 地址和端口號(hào)(在連接建立后有效)。
2.3 -> QByteArray 與 QString 的轉(zhuǎn)換
由于網(wǎng)絡(luò)傳輸?shù)氖亲止?jié)流,而 Qt 程序中常使用 QString 處理文本,因此兩者間的轉(zhuǎn)換非常常見(jiàn):
- QString → QByteArray
QString str = "Hello"; QByteArray data = str.toUtf8(); // 常用 UTF-8 編碼
- QByteArray → QString
QByteArray data = ...; QString str = QString::fromUtf8(data);
3 -> 通信流程概述
3.1 -> 服務(wù)器端典型流程
- 創(chuàng)建
QTcpServer,監(jiān)聽(tīng)指定端口。 - 在
newConnection()槽中接受新連接,獲取QTcpSocket。 - 為該 socket 連接
readyRead()信號(hào),在槽函數(shù)中讀取請(qǐng)求數(shù)據(jù),處理業(yè)務(wù),并回復(fù)響應(yīng)。 - 連接
disconnected()信號(hào),在客戶端斷開(kāi)時(shí)進(jìn)行資源清理(如調(diào)用deleteLater()刪除 socket 對(duì)象)。
3.2 -> 客戶端典型流程
- 創(chuàng)建
QTcpSocket,調(diào)用connectToHost()連接服務(wù)器。 - 連接
connected()信號(hào),確認(rèn)連接成功。 - 連接
readyRead()信號(hào),接收服務(wù)器返回的數(shù)據(jù)。 - 使用
write()發(fā)送請(qǐng)求數(shù)據(jù)。 - 在適當(dāng)時(shí)機(jī)斷開(kāi)連接或處理異常斷開(kāi)。
4 -> 代碼示例
4.1 -> 回顯客戶端
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 1. 創(chuàng)建 QTcpServer 的實(shí)例
socket = new QTcpSocket(this);
// 2. 設(shè)置標(biāo)題
this->setWindowTitle("客戶端");
// 3. 和服務(wù)器建立連接
socket->connectToHost("127.0.0.1", 9090);
// 4. 連接信號(hào)槽, 處理響應(yīng)
connect(socket, &QTcpSocket::readyRead, this, [=](){
// a) 讀取出響應(yīng)內(nèi)容
QString response = socket->readAll();
// b) 把響應(yīng)內(nèi)容顯示到界面上
ui->listWidget->addItem("服務(wù)器說(shuō): " + response);
});
// 5. 等待連接建立的結(jié)果. 確認(rèn)是否連接成功
bool ret = socket->waitForConnected();
if (!ret)
{
QMessageBox::critical(this, "連接服務(wù)器出錯(cuò)", socket->errorString());
exit(1);
}
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
// 1. 獲取到輸入框中的內(nèi)容
const QString& text = ui->lineEdit->text();
// 2. 發(fā)送數(shù)據(jù)給服務(wù)器
socket->write(text.toUtf8());
// 3. 把發(fā)的消息顯示到界面上
ui->listWidget->addItem("客戶端說(shuō): " + text);
// 4. 清空輸入框的內(nèi)容
ui->lineEdit->setText("");
}
4.2 -> 回顯服務(wù)端
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QTcpSocket>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 1. 創(chuàng)建 QTcpServer 的實(shí)例
tcpServer = new QTcpServer(this);
// 2. 設(shè)置標(biāo)題
this->setWindowTitle("服務(wù)器");
// 3. 通過(guò)信號(hào)槽, 指定如何處理連接
connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection);
bool ret = tcpServer->listen(QHostAddress::Any, 9090);
if (!ret)
{
QMessageBox::critical(this, "服務(wù)器啟動(dòng)失敗!", tcpServer->errorString());
exit(1);
}
}
Widget::~Widget()
{
delete ui;
}
void Widget::processConnection()
{
// 1. 通過(guò) tcpServer 拿到一個(gè) socket 對(duì)象, 通過(guò)這個(gè)對(duì)象來(lái)和客戶端進(jìn)行通信
QTcpSocket* clientSocket = tcpServer->nextPendingConnection();
QString log = "[ " + clientSocket->peerAddress().toString() + ": " + QString::number(clientSocket->peerPort()) +
" ] 客戶端上線!";
ui->listWidget->addItem(log);
// 2. 通過(guò)信號(hào)槽, 來(lái)處理客戶端發(fā)來(lái)請(qǐng)求的情況
connect(clientSocket, &QTcpSocket::readyRead, this, [=](){
// a) 讀取出請(qǐng)求數(shù)據(jù). 此處 readAll 返回的是 QByteArray, 通過(guò)賦值轉(zhuǎn)成 QString
QString request = clientSocket->readAll();
// b) 根據(jù)請(qǐng)求處理響應(yīng)
const QString& response = process(request);
// c) 把響應(yīng)寫(xiě)回到客戶端
clientSocket->write(response.toUtf8());
// d) 把上述信息記錄到日志中
QString log = "[ " + clientSocket->peerAddress().toString() + ": " + QString::number(clientSocket->peerPort()) + " ] "
+ "req: " + request + ", " + "resp: " + response;
ui->listWidget->addItem(log);
});
// 3. 通過(guò)信號(hào)槽, 來(lái)處理客戶端斷開(kāi)連接的情況
connect(clientSocket, &QTcpSocket::disconnected, this, [=](){
// a) 把斷開(kāi)連接的信息通過(guò)日志顯示出來(lái)
QString log = "[ " + clientSocket->peerAddress().toString() + ": " + QString::number(clientSocket->peerPort()) +
" ] 客戶端下線!";
ui->listWidget->addItem(log);
// b) 手動(dòng)釋放 clientSocket. 直接使用 delete 是下策, 使用 deleteLater 更加合適的.
// delete clientSocket;
clientSocket->deleteLater();
});
}
// 此處寫(xiě)的是回顯服務(wù)器
QString Widget::process(const QString request)
{
return request;
}

5 -> 總結(jié)
Qt 的 TCP 網(wǎng)絡(luò)編程模塊通過(guò) QTcpServer 和 QTcpSocket 提供了高度封裝、易于使用的 API,大大降低了網(wǎng)絡(luò)應(yīng)用程序的開(kāi)發(fā)難度。其特點(diǎn)可總結(jié)如下:
- 面向?qū)ο笈c事件驅(qū)動(dòng):通過(guò)信號(hào)與槽機(jī)制處理連接、數(shù)據(jù)接收、斷開(kāi)等事件,代碼結(jié)構(gòu)清晰。
- 跨平臺(tái)性:同一套代碼可在主流操作系統(tǒng)上運(yùn)行,無(wú)需關(guān)心底層 Socket API 差異。
- 與 Qt 生態(tài)無(wú)縫集成:可方便地與 GUI、多線程、定時(shí)器、文件 IO 等其他 Qt 模塊結(jié)合使用。
- 高效的異步 IO:基于事件循環(huán),無(wú)需阻塞線程即可處理多個(gè)并發(fā)連接,適合高性能服務(wù)器開(kāi)發(fā)。
- 良好的錯(cuò)誤處理機(jī)制:提供詳細(xì)的錯(cuò)誤碼和描述,便于調(diào)試和異常處理。
在實(shí)際開(kāi)發(fā)中,除了掌握上述基礎(chǔ) API 外,還需注意資源管理(及時(shí)釋放 socket)、協(xié)議設(shè)計(jì)(定義清晰的數(shù)據(jù)包格式以處理粘包/半包問(wèn)題)、多線程處理(將耗時(shí)操作移至子線程以避免阻塞主循環(huán))等高級(jí)主題。Qt 的網(wǎng)絡(luò)模塊為構(gòu)建穩(wěn)定、高效的 C++ 網(wǎng)絡(luò)應(yīng)用提供了堅(jiān)實(shí)的基礎(chǔ),是開(kāi)發(fā)跨平臺(tái)客戶端/服務(wù)器程序的優(yōu)秀選擇。
到此這篇關(guān)于Qt中TCP Socket的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Qt TCP Socket內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言中計(jì)算二叉樹(shù)的寬度的兩種方式
這篇文章主要介紹了C語(yǔ)言中計(jì)算二叉樹(shù)的寬度的兩種方式的相關(guān)資料,需要的朋友可以參考下2017-04-04
C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單電子通訊錄(2)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單電子通訊錄的第二部分,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06
通過(guò)“回文字算法”復(fù)習(xí)C++語(yǔ)言
這篇文章主要介紹了通過(guò)“回文字算法”復(fù)習(xí)C++語(yǔ)言的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-10-10
C++ 類的構(gòu)造函數(shù)詳解及實(shí)例
這篇文章主要介紹了C++ 類的構(gòu)造函數(shù)詳解及實(shí)例的相關(guān)資料,學(xué)習(xí)C++ 的朋友對(duì)構(gòu)造函數(shù)肯定不陌生,非常重要的基礎(chǔ)知識(shí),這里就詳細(xì)介紹下,需要的朋友可以參考下2016-12-12
C語(yǔ)言文件操作中 fgets與fputs 函數(shù)詳解
這篇文章主要介紹了C語(yǔ)言文件操作中 fgets與fputs 函數(shù)詳解的相關(guān)資料,需要的朋友可以參考下2017-06-06

