學習目標:?TCP網絡通信編程
前置環境
運行環境:qt?creator 4.12
學習內容
一、TCP 協議基礎知識:
- TCP 是一種面向連接的、可靠的、基于字節流的傳輸層通信協議。
- TCP 擁塞控制算法包括慢啟動、擁塞避免、快速重傳和快速恢復。
- TCP 通信需要建立連接,Qt 提供 QTcpSocket 和 QTcpServer 類用于 TCP 客戶端和服務器編程。
- QTcpServer 類用于建立網絡監聽和 socket 連接,主要接口函數如 listen()、newConnection() 等。
QTcpServer* tcpServer; 它負責監聽指定的 IP 地址和端口,并在有新的客戶端連接時發出 newConnection() 信號。
QTcpSocket* tcpSocket; 它代表一個與服務端建立的 TCP 連接。
QTcpServer類
QTcpServer 是 Qt 框架提供的 TCP 服務器類,它提供了一系列常用的成員函數和信號,用于實現 TCP 服務器的基本功能。以下是 QTcpServer 的一些常用成員函數:
-
構造函數和析構函數:
QTcpServer(QObject *parent = nullptr)
:創建一個 QTcpServer 對象。~QTcpServer()
:析構函數,用于釋放資源。
-
服務器狀態控制:
bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)
:開始監聽指定的地址和端口。void close()
:停止監聽并關閉服務器。-
abort()是強行立即關閉連接,相當于調用disconnectFromHost()。關閉后會釋放所有相關資源并觸發 QAbstractSocket::abort信號。對端可能不知道連接已經關閉。
-
close()是通過標準關閉序列完成關閉,等待對端確認,保證數據完整性。它會優雅地關閉socket連接,先發送關閉請求給對端,并等待對端確認。然后會進入關閉狀態,觸發QAbstractSocket::stateChanged(QAbstractSocket::ClosingState)和QAbstractSocket::disconnected信號。
-
bool isListening() const
:檢查服務器是否正在監聽。
-
獲取服務器信息:
QHostAddress serverAddress() const
:返回服務器綁定的地址。quint16 serverPort() const
:返回服務器綁定的端口。QAbstractSocket::SocketError socketError() const
:返回最近一次發生的套接字錯誤。QString errorString() const
:返回最近一次發生的錯誤的描述字符串。
-
新連接管理:
QTcpSocket *nextPendingConnection()
:返回下一個待處理的連接。int hasPendingConnections() const
:返回待處理連接的數量。
-
信號處理:
void newConnection()
:當有新的連接到達時發出此信號。void acceptError(QAbstractSocket::SocketError socketError)
:當接受新連接時發生錯誤時發出此信號。
-
其他功能:
void setMaxPendingConnections(int numConnections)
:設置服務器可以同時處理的最大待處理連接數。int maxPendingConnections() const
:返回服務器的最大待處理連接數。void setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value)
:設置套接字選項。QVariant socketOption(QAbstractSocket::SocketOption option) const
:獲取套接字選項的當前值。
-
地址和端口設置:
void setAddress(const QHostAddress &address)
:設置服務器監聽的地址。QHostAddress address() const
:返回服務器監聽的地址。void setPort(quint16 port)
:設置服務器監聽的端口。quint16 port() const
:返回服務器監聽的端口。
-
SSL/TLS 支持:
void setSecureMode(QSsl::SslMode mode)
:設置服務器的 SSL/TLS 模式。QSsl::SslMode secureMode() const
:返回服務器的 SSL/TLS 模式。void setLocalCertificate(const QSslCertificate &certificate)
:設置服務器的本地證書。QSslCertificate localCertificate() const
:返回服務器的本地證書。void setPrivateKey(const QSslKey &key)
:設置服務器的私鑰。QSslKey privateKey() const
:返回服務器的私鑰。
-
線程安全:
void setThreadPool(QThreadPool *threadPool)
:設置用于處理新連接的線程池。QThreadPool *threadPool() const
:返回用于處理新連接的線程池。
-
日志記錄:
void setProxy(const QNetworkProxy &proxy)
:設置代理服務器。QNetworkProxy proxy() const
:返回當前使用的代理服務器。
這些成員函數提供了更多的靈活性和控制能力,使開發者能夠根據具體需求配置和管理 TCP 服務器。例如,可以設置 SSL/TLS 模式以提供安全的通信,使用線程池來提高并發處理能力,以及設置代理服務器以實現更復雜的網絡拓撲。
QTcpServer
?類的使用:
QTcpServer
?是從?QObject
?繼承的類,用于服務器建立網絡監聽和創建網絡 socket 連接。- 主要接口函數包括?
listen()
、nextPendingConnection()
、serverAddress()
?和?serverPort()
?等。
服務器Server提供的回調函數
在 Qt TCP 編程中,主要提供了以下幾種重要的回調接口:
-
QTcpServer
?相關的回調:QTcpServer::newConnection()
: 當有新的客戶端連接到達時觸發該信號。QTcpServer::acceptError(QAbstractSocket::SocketError socketError)
: 當服務器無法接受新的連接時觸發該信號,可以獲取錯誤信息。
-
QTcpSocket
?相關的回調:QTcpSocket::connected()
: 當套接字成功連接到遠程主機時觸發該信號。QTcpSocket::disconnected()
: 當套接字斷開連接fin 位 為1時觸發該信號。QTcpSocket::readyRead()
: 當套接字有新數據可讀時觸發該信號。QTcpSocket::bytesWritten(qint64 bytes)
: 當成功寫入數據到套接字時觸發該信號,并返回寫入的字節數。QTcpSocket::error(QAbstractSocket::SocketError socketError)
: 當套接字發生錯誤時觸發該信號,可以獲取錯誤信息。QTcpSocket::stateChanged(QAbstractSocket::SocketState socketState)
: 當套接字的連接狀態發生變化時觸發該信號。-
bytesWritten(qint64 bytes)
?信號:- 當成功寫入數據到?
QTcpSocket
?時會觸發此信號。 bytes
?參數表示成功寫入的字節數。- 可以用來監控數據發送的進度。
- 當成功寫入數據到?
-
aboutToClose()
?信號:- 當?
QTcpSocket
?將要關閉時會觸發此信號。 - 可以在此信號的槽函數中執行一些數據刷新或保存操作。
- 當?
-
hostFound()
?信號:- 當?
QTcpSocket
?成功解析了主機地址時會觸發此信號。 - 可以用來監控 DNS 解析的進度。
- 當?
-
QSslSocket
?相關的回調(用于 SSL/TLS 連接):QSslSocket::sslErrors(const QList<QSslError> &errors)
: 當 SSL/TLS 連接發生錯誤時觸發該信號。QSslSocket::encrypted()
: 當 SSL/TLS 連接成功加密時觸發該信號。
-
QNetworkProxy
?相關的回調:QTcpSocket::proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator)
: 當需要代理服務器認證時觸發該信號。
這些回調接口涵蓋了 TCP 連接的整個生命周期,包括連接建立、數據交互、連接斷開以及各種錯誤情況。通過監聽和處理這些信號,我們可以更好地控制和管理 TCP 連接,提高應用程序的可靠性和健壯性。
QT TCP網絡通信編程項目
TCP服務端代碼
#include "mainwindow.h"
#include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);setWindowTitle("TCP通訊服務端");//獲取主機名和ip地址QHostInfo info =QHostInfo::fromName(QHostInfo::localHostName());QList<QHostAddress> addrs=info.addresses();if(!addrs.empty()){foreach(const QHostAddress & addr,addrs){if(addr.protocol()==QAbstractSocket::IPv4Protocol){ui->serverip->addItem(addr.toString());}}}tcpServer=new QTcpServer(this);//注冊新連接回調 表示有新的客戶端連接到達服務器。connect(tcpServer,&QTcpServer::newConnection,this,&MainWindow::ConnectCallback);}void MainWindow::ConnectCallback(){ //連接到達tcpSocket = tcpServer->nextPendingConnection();//連接成功后回調 表示當前 TCP 連接已經成功建立。auto clientconnect=[this](){// 客戶端連接ui->plainTextEdit->appendPlainText("**********客戶端socket連接成功**********");ui->plainTextEdit->appendPlainText("**********peer address:"+tcpSocket->peerAddress().toString());ui->plainTextEdit->appendPlainText("**********peer port:"+QString::number(tcpSocket->peerPort()));};connect(tcpSocket,&QTcpSocket::connected,this,clientconnect);clientconnect(); //因為當前連接已經到達了 所有手動調用一次//讀前回調 當 tcpSocket 有數據可讀時,該信號會被觸發connect(tcpSocket,&QTcpSocket::readyRead,this,[this](){while(tcpSocket->canReadLine()){QString result = "ip:%1 prot:%2 in:%3 ";result = result.arg(tcpSocket->peerAddress().toString()).arg(QString::number(tcpSocket->peerPort())).arg(QString(tcpSocket->readLine()));ui->plainTextEdit->appendPlainText(result);}});//錯誤回調 當 tcpSocket 發生錯誤時,該信號會被觸發connect(tcpSocket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error),this, [this](QAbstractSocket::SocketError error) {// ui->plainTextEdit->appendPlainText("Socket error:" + error + tcpSocket->errorString());});//當套接字的連接狀態發生變化時,該信號會被觸發,并且會傳遞一個 QAbstractSocket::SocketState 類型的參數,表示當前的連接狀態。connect(tcpSocket,QOverload<QAbstractSocket::SocketState>::of(&QTcpSocket::stateChanged),this,[this](QTcpSocket::SocketState state){// ui->plainTextEdit->appendPlainText("套接字狀態變更:" + state);});//斷開連接 fin位確認 回調connect(tcpSocket,&QTcpSocket::disconnected,this,[this](){// 客戶端斷開連接QString result = "**********客戶端socket斷開連接[ip:%1,prot:%2,]";result = result.arg(tcpSocket->peerAddress().toString()).arg(QString::number(tcpSocket->peerPort()));ui->plainTextEdit->appendPlainText(result);tcpSocket->deleteLater();});
}
MainWindow::~MainWindow()
{delete ui;if(tcpServer->isListening()){// 關閉TCP服務器tcpServer->close();tcpServer->deleteLater(); //最終關閉qDebug() << "TCP server MainWindow.";}
}
void MainWindow::closeEvent(QCloseEvent *e){ //關閉窗口//關閉closeMainWindow::on_stopServer_clicked();e->accept(); //允許窗口完成關閉操作。
}void MainWindow::on_startServer_clicked()
{QString ip(ui->serverip->currentText());uint16_t port =ui->serverport->value();tcpServer->listen(QHostAddress(ip),port);ui->plainTextEdit->appendPlainText("$$$$$$$$$$開始監聽$$$$$$$$$$");ui->plainTextEdit->appendPlainText("服務器地址:"+tcpServer->serverAddress().toString());ui->plainTextEdit->appendPlainText("服務器端口:"+QString::number(tcpServer->serverPort()));ui->startServer->setEnabled(false);ui->stopServer->setEnabled(true);}void MainWindow::on_stopServer_clicked() //關閉tcpserver
{//先關閉所有socketif(!tcpSocket){tcpSocket->disconnect(); //用于斷開 QTcpSocket 對象的所有信號與槽的連接。tcpSocket->close(); //它會向對端發送 FIN 數據包,并等待對端的確認,完成 TCP 連接的正常關閉過程。//fin回調 已調用 tcpSocket->deleteLater(); //它不會立即刪除對象,而是將其標記為待刪除狀態,等到當前事件循環結束后再執行刪除操作。}if(tcpServer->isListening()){tcpServer->close();//不調用 deleteLater 為了下次再次開啟ui->startServer->setEnabled(true);ui->stopServer->setEnabled(false);ui->plainTextEdit->clear();}}void MainWindow::on_sendmsg_clicked()
{QString msg =ui->lineEdit->text();ui->lineEdit->clear();ui->plainTextEdit->appendPlainText("[out]:"+msg);tcpSocket->write(msg.toUtf8()+'\n');}
TCP客戶端代碼
#include "mainwindow.h"
#include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);setWindowTitle("tcp通信客戶端");QHostInfo info =QHostInfo::fromName(QHostInfo::localHostName());QList<QHostAddress> addrs =info.addresses();if(!addrs.empty()){foreach(const QHostAddress& addr ,addrs){if(addr.protocol() == QAbstractSocket::IPv4Protocol){ui->serverip->addItem(addr.toString());}}}client = new QTcpSocket();//當 socket 成功連接到服務器時,會發射 connected() 信號。connect(client,&QTcpSocket::connected,this,[this](){ui->plainTextEdit->appendPlainText("**********已經連接到服務器端**********");ui->plainTextEdit->appendPlainText("服務器端ip:"+client->peerAddress().toString());ui->plainTextEdit->appendPlainText("服務器端port:"+QString::number(client->peerPort()));ui->tcpconnect->setEnabled(false);ui->tcpclose->setEnabled(true);});//當 socket 與服務器斷開連接時,會發射 disconnected() 信號。connect(client,&QTcpSocket::disconnected,this,[this](){ui->plainTextEdit->appendPlainText("**********已斷開與服務器端的連接**********");client->close();ui->tcpconnect->setEnabled(true);ui->tcpclose->setEnabled(false);});//當 socket 有新的數據可讀時,會發射 readyRead() 信號。connect(client,&QTcpSocket::readyRead,this,[this](){while(client->canReadLine()){ui->plainTextEdit->appendPlainText("[in]:"+client->readLine());}});}MainWindow::~MainWindow()
{delete ui;
}\void MainWindow::on_send_clicked()
{QString msg=ui->lineEdit->text();ui->plainTextEdit->appendPlainText("[out]:"+msg);ui->lineEdit->clear();client->write(msg.toUtf8()+'\n');}void MainWindow::on_tcpconnect_clicked()
{QString ip=ui->serverip->currentText();quint16 port =ui->serverport->value();client->connectToHost(ip,port);if (!client->waitForConnected(3000)) {// 5s 連接超時處理邏輯ui->plainTextEdit->appendPlainText("連接超時,請檢查服務器ip和port是否正確.");}}void MainWindow::on_tcpclose_clicked()
{if(client->state() ==QAbstractSocket::ConnectedState) client->disconnectFromHost(); //這里首先檢查 tcpclient 對象的當前連接狀態。ui->tcpconnect->setEnabled(true);ui->tcpclose->setEnabled(false);}
// 在應用程序退出或客戶端斷開連接時 點關閉窗口
void MainWindow::closeEvent(QCloseEvent* event) {// 1. 斷開與服務器的連接if (client->state() == QAbstractSocket::ConnectedState) {client->disconnectFromHost();}qDebug()<<"closeEvent";// 2. 釋放 QTcpSocket 對象client->deleteLater();// 允許窗口關閉event->accept();
}
?總結
server端
-
TCP 服務器的創建和啟停:
- 在構造函數中創建?
QTcpServer
?對象,并連接?newConnection()
?信號到?ConnectCallback
?函數。 on_startServer_clicked()
?函數中,監聽指定的 IP 和端口,啟動 TCP 服務器。on_stopServer_clicked()
?函數中,關閉 TCP 服務器,斷開所有客戶端連接。- 在主窗口關閉時,調用?
on_stopServer_clicked()
?函數關閉服務器。
- 在構造函數中創建?
-
客戶端連接的處理:
- 在?
ConnectCallback
?函數中,獲取新連接的?QTcpSocket
?對象。 - 連接客戶端連接成功、數據可讀、錯誤、狀態變更、斷開連接等信號。
- 在信號處理函數中,輸出連接信息并處理數據收發。
- 在?
-
數據收發和協議處理:
- 連接?
readyRead()
?信號,處理客戶端發送的數據。 - 使用?
tcpSocket->readLine()
?讀取并解析數據,輸出到 UI 界面。 on_sendmsg_clicked()
?函數中,通過?tcpSocket->write()
?將消息發送給客戶端。
- 連接?
-
異常處理:
- 連接?
error()
?信號,處理套接字錯誤。 - 連接?
stateChanged()
?信號,監控連接狀態變更。
- 連接?
-
生命周期管理:
- 在主窗口析構函數中,關閉 TCP 服務器并釋放資源。
- 在客戶端斷開連接時,釋放?
QTcpSocket
?對象。-
如果需要立即關閉連接而不管數據完整性,使用abort()。
-
如果需要負責任關閉保證數據完整,則使用close()。
-
通過學習這段代碼,我們可以掌握以下 Qt TCP 編程的關鍵點:
- 如何創建 TCP 服務器并監聽端口。
- 如何處理新的客戶端連接,并與之進行數據通信。
- 如何處理連接錯誤和狀態變更。
- 如何優雅地關閉服務器并釋放資源。
客戶端
-
TCP 客戶端的創建和連接:
- 在構造函數中創建?
QTcpSocket
?對象?client
。 - 連接?
connected()
?信號,處理與服務器成功連接的情況。 on_tcpconnect_clicked()
?函數中,調用?connectToHost()
?連接到服務器。- 使用?
waitForConnected()
?處理連接超時的情況。
- 在構造函數中創建?
-
數據收發和協議處理:
- 連接?
readyRead()
?信號,處理服務器發送的數據。 - 使用?
client->readLine()
?讀取并解析數據,輸出到 UI 界面。 on_send_clicked()
?函數中,通過?client->write()
?將消息發送給服務器。
- 連接?
-
連接狀態管理:
- 連接?
disconnected()
?信號,處理與服務器的斷開連接。 - 在?
closeEvent()
?中,檢查連接狀態并斷開連接。 - 更新 UI 按鈕的狀態,反映當前的連接狀態。
- 連接?
-
異常處理:
- 在連接超時的情況下,輸出錯誤提示信息。
- 在?
closeEvent()
?中,釋放?QTcpSocket
?對象。
通過學習這段代碼,我們可以掌握以下 Qt TCP 客戶端編程的關鍵點:
- 如何創建 TCP 客戶端并連接到服務器。
- 如何處理數據的收發,并按照約定的協議進行解析。
- 如何管理連接狀態,處理連接成功、斷開等情況。
- 如何優雅地關閉連接并釋放資源。
? ??
最后附上源代碼鏈接
對您有幫助的話,幫忙點個star
35-tcpSocket-client · jbjnb/Qt demo - 碼云 - 開源中國 (gitee.com)
35-tcpSocket-server · jbjnb/Qt demo - 碼云 - 開源中國 (gitee.com)