Qt网络模块基础
使用Qt进行UDP/TCP网络编程,需先在项目的.pro文件中引入network模块,否则无法使用网络相关类:
QT += network
该模块封装了底层操作系统的网络API,实现跨平台(Windows/Linux/macOS)的网络通信开发,无需关注不同系统的网络接口差异。
UDP编程
UDP协议核心特性
UDP(User Datagram Protocol,用户数据报协议)是无连接、不可靠、基于数据报的传输层协议:
- 无连接:通信前无需建立连接(无三次握手),客户端可直接向服务器发送数据报;
- 不可靠:不保证数据到达、到达顺序与发送一致,无重传机制,丢包由应用层自行处理;
- 高效快速:协议头部仅8字节,传输开销小,延迟低;
- 适用场景:实时音视频传输、游戏数据同步、广播/组播通信、物联网轻量数据传输等。
UDP核心编程接口:QUdpSocket
Qt通过QUdpSocket类实现UDP通信,该类兼具客户端和服务器功能,核心函数/信号如下:
| 类型 | 名称 | 核心作用 |
|---|---|---|
| 函数 | bind() | 绑定IP和端口,服务器用于监听,客户端可选绑定(不绑定则系统自动分配端口) |
| 函数 | writeDatagram() | 发送UDP数据报到指定IP和端口 |
| 函数 | readDatagram() | 读取接收到的UDP数据报(含发送方IP/端口) |
| 函数 | hasPendingDatagrams() | 判断是否有未处理的待读取数据报 |
| 函数 | pendingDatagramSize() | 获取下一个待读取数据报的字节数 |
| 信号 | readyRead() | 有数据可读取时触发(核心信号) |
| 函数 | errorString() | 获取最近一次操作的错误描述(用于故障排查) |
UDP开发流程
UDP服务器流程
- 创建
QUdpSocket对象; - 调用
bind()绑定监听的IP和端口(如QHostAddress::LocalHost:8899); - 关联
readyRead()信号到槽函数,处理接收的数据; - (可选)通过
writeDatagram()向客户端回发数据。
UDP客户端流程
- 创建
QUdpSocket对象; - (可选)调用
bind()绑定本地端口(不绑定则系统自动分配); - 调用
writeDatagram()向服务器的IP和端口发送数据; - (可选)关联
readyRead()信号,接收服务器回发的数据。
UDP完整示例
UDP服务器代码
#include <QCoreApplication>
#include <QtNetwork/QUdpSocket>
#include <QDebug>int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 创建UDP套接字对象QUdpSocket udpServerSocket;/*** @brief bind 绑定IP和端口,使服务器监听该端口的UDP数据* @param address 监听的IP地址:QHostAddress::LocalHost(仅本地访问,127.0.0.1)、QHostAddress::Any(所有网卡,0.0.0.0)* @param port 监听的端口号(建议1024-65535,避免占用系统保留端口1-1023)* @param mode 绑定模式(默认QUdpSocket::DefaultForPlatform,无需手动指定)* @return bool 绑定成功返回true,失败返回false(如端口被占用)*/bool bindResult = udpServerSocket.bind(QHostAddress::LocalHost, 8899);if (!bindResult) {qDebug() << "UDP服务器绑定失败:" << udpServerSocket.errorString();return -1;}qDebug() << "UDP服务器启动成功,监听 127.0.0.1:8899";// 关联数据接收信号:有数据到达时触发槽函数QObject::connect(&udpServerSocket, &QUdpSocket::readyRead, [&]() {// 循环读取所有待处理的数据报(避免漏读)while (udpServerSocket.hasPendingDatagrams()) {/*** @brief pendingDatagramSize 获取下一个数据报的字节数* @return qint64 数据报长度,失败返回-1*/QByteArray datagram;datagram.resize(udpServerSocket.pendingDatagramSize()); // 调整数组大小匹配数据报QHostAddress senderAddr; // 输出参数:发送方IP地址quint16 senderPort; // 输出参数:发送方端口号/*** @brief readDatagram 读取UDP数据报* @param data 存储数据的缓冲区指针* @param size 缓冲区大小(需≥数据报长度)* @param sender 输出参数:发送方IP地址* @param senderPort 输出参数:发送方端口号* @return qint64 成功读取的字节数,失败返回-1*/qint64 readLen = udpServerSocket.readDatagram(datagram.data(), datagram.size(), &senderAddr, &senderPort);if (readLen > 0) {qDebug() << "收到来自" << senderAddr.toString() << ":" << senderPort<< "的消息:" << datagram;// 可选:向客户端回发确认数据QByteArray replyData = "服务器已接收:" + datagram;/*** @brief writeDatagram 发送UDP数据报* @param data 待发送的字节数组* @param address 目标IP地址(此处为客户端IP)* @param port 目标端口号(此处为客户端端口)* @return qint64 成功发送的字节数,失败返回-1*/qint64 sendLen = udpServerSocket.writeDatagram(replyData, senderAddr, senderPort);if (sendLen == -1) {qDebug() << "回发数据失败:" << udpServerSocket.errorString();}}}});return a.exec(); // 启动事件循环,保持服务器运行
}
UDP客户端代码
#include <QCoreApplication>
#include <QtNetwork/QUdpSocket>
#include <QDebug>int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 创建UDP套接字对象QUdpSocket udpClientSocket;// 待发送的数据和服务器地址QByteArray sendData = "Hello UDP Server!";QHostAddress serverAddr = QHostAddress::LocalHost; // 服务器IPquint16 serverPort = 8899; // 服务器端口/*** @brief writeDatagram 发送UDP数据报到指定服务器* @param data 待发送的字节数组* @param address 服务器IP地址* @param port 服务器端口号* @return qint64 成功发送的字节数,失败返回-1(如网络不可达)*/qint64 sendLen = udpClientSocket.writeDatagram(sendData, serverAddr, serverPort);if (sendLen == -1) {qDebug() << "发送数据失败:" << udpClientSocket.errorString();return -1;}qDebug() << "成功发送" << sendLen << "字节到" << serverAddr.toString() << ":" << serverPort;// 接收服务器回发的数据QObject::connect(&udpClientSocket, &QUdpSocket::readyRead, [&]() {while (udpClientSocket.hasPendingDatagrams()) {QByteArray datagram;datagram.resize(udpClientSocket.pendingDatagramSize());QHostAddress senderAddr;quint16 senderPort;qint64 readLen = udpClientSocket.readDatagram(datagram.data(), datagram.size(), &senderAddr, &senderPort);if (readLen > 0) {qDebug() << "收到服务器回发:" << QString::fromUtf8(datagram);}}});return a.exec(); // 保持客户端运行,等待接收回发数据
}


UDP编程注意事项
- 数据报大小:UDP单包最大约65507字节(IPv4),超过会被IP层分片,建议单包控制在1472字节(适配MTU,避免分片丢包);
- 端口冲突:绑定端口失败时,优先检查端口是否被其他程序占用;
- 广播/组播:UDP支持广播(
QHostAddress::Broadcast)和组播,TCP不支持; - 无连接特性:服务器无需“接受连接”,只要绑定端口就能接收任意客户端的数据。
TCP编程
TCP协议核心特性
TCP(Transmission Control Protocol,传输控制协议)是面向连接、可靠、基于字节流的传输层协议:
- 面向连接:通信前需三次握手建立连接,通信后四次挥手断开连接;
- 可靠性:通过确认应答、重传机制、序号/确认号保证数据按序、完整到达;
- 面向字节流:无数据报边界,接收方可能将多次发送的小数据合并(粘包),需应用层处理;
- 拥塞控制:内置滑动窗口、慢启动等机制,适配网络拥塞状态;
- 适用场景:HTTP/HTTPS、文件传输(FTP)、邮件(SMTP/POP3)、即时通讯等要求数据可靠的场景。
TCP核心编程接口
| 类名 | 核心作用 | 关键函数/信号 |
|---|---|---|
| QTcpServer | 服务器端核心类,用于监听客户端连接 | listen():启动端口监听 newConnection():有新客户端连接时触发 nextPendingConnection():获取与新客户端通信的QTcpSocket |
| QTcpSocket | 客户端/服务器通信类,用于数据收发 | connectToHost():客户端连接服务器 write():发送数据 readAll():读取所有接收数据 readyRead():有数据可读取时触发 connected():连接成功时触发 disconnected():连接断开时触发 state():获取连接状态(如ConnectedState) |
| QHostAddress | 表示IP地址 | 支持IPv4(如QHostAddress("192.168.1.1"))、IPv6,常用常量:QHostAddress::Any(0.0.0.0)、QHostAddress::LocalHost(127.0.0.1) |
| QNetworkInterface | 获取本机网络接口信息 | allAddresses():获取本机所有IP地址 allInterfaces():获取所有网络接口(含MAC、子网掩码) |
TCP开发流程
TCP服务器流程
- 创建
QTcpServer对象; - 调用
listen()绑定IP和端口,启动监听; - 关联
newConnection()信号到槽函数,处理新客户端连接; - 在槽函数中调用
nextPendingConnection()获取QTcpSocket对象(与客户端通信); - 关联
QTcpSocket的readyRead()信号,处理客户端发送的数据; - (可选)关联
disconnected()信号,处理客户端断开连接; - 通过
QTcpSocket::write()向客户端发送数据。
TCP客户端流程
- 创建
QTcpSocket对象; - 调用
connectToHost()连接服务器IP和端口; - (可选)关联
connected()信号,处理连接成功事件; - 调用
write()向服务器发送数据; - 关联
readyRead()信号,处理服务器回发的数据; - (可选)关联
disconnected()信号,处理连接断开; - 通信结束后调用
disconnectFromHost()断开连接。
TCP完整示例
项目配置(.pro 文件)
QT += core gui network widgetsCONFIG += c++11# 生成可执行文件名称
TARGET = TcpGuiDemo
TEMPLATE = app# 源文件
SOURCES += main.cpp \servertcp.cpp \clienttcp.cpp# 头文件
HEADERS += servertcp.h \clienttcp.h# UI文件(客户端界面)
FORMS += clienttcp.ui
TCP 服务器代码(无 UI,后台运行)
servertcp.h
#ifndef SERVERTCP_H
#define SERVERTCP_H
#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
#include <QAbstractSocket>
class ServerTcp : public QObject{Q_OBJECT
public:explicit ServerTcp(QObject *parent = nullptr) : QObject(parent) {initServer();}private:QTcpServer m_server; // TCP服务器核心对象void initServer() {// 监听所有网卡的9999端口bool listenOk = m_server.listen(QHostAddress::Any, 9999);if (!listenOk) {qDebug() << "[服务器] 启动失败:" << m_server.errorString();return;}qDebug() << "[服务器] 启动成功,监听 0.0.0.0:9999";// 关联新客户端连接信号connect(&m_server, &QTcpServer::newConnection, this, &ServerTcp::handleNewClient);}private slots:// 处理新客户端连接void handleNewClient() {QTcpSocket *clientSocket = m_server.nextPendingConnection();if (!clientSocket) {qDebug() << "[服务器] 获取客户端套接字失败";return;}// 打印客户端信息QString clientInfo = QString("%1:%2").arg(clientSocket->peerAddress().toString()).arg(clientSocket->peerPort());qDebug() << "[服务器] 新客户端连接:" << clientInfo;// 发送欢迎消息clientSocket->write(QString("欢迎连接TCP服务器![%1]").arg(clientInfo).toUtf8());// 关联客户端数据接收信号connect(clientSocket, &QTcpSocket::readyRead, this, &ServerTcp::handleClientData);// 关联客户端断开连接信号connect(clientSocket, &QTcpSocket::disconnected, this, [=]() {qDebug() << "[服务器] 客户端断开连接:" << clientInfo;clientSocket->deleteLater(); // 释放资源});// 关联错误信号(Qt 5.15+ 推荐用&QTcpSocket::errorOccurred)connect(clientSocket, static_cast<void (QTcpSocket::*)(QAbstractSocket::SocketError)>(&QTcpSocket::error), this, [=](QAbstractSocket::SocketError err) {qDebug() << "[服务器] 客户端错误(" << err << "):" << clientSocket->errorString();clientSocket->disconnectFromHost();clientSocket->deleteLater();});}// 处理客户端发送的数据void handleClientData() {QTcpSocket *clientSocket = qobject_cast<QTcpSocket*>(sender());if (!clientSocket) return;// 读取客户端数据QByteArray recvData = clientSocket->readAll();QString recvStr = QString::fromUtf8(recvData).trimmed(); // 去除首尾空白/换行QString clientInfo = QString("%1:%2").arg(clientSocket->peerAddress().toString()).arg(clientSocket->peerPort());qDebug() << "[服务器] 收到" << clientInfo << "数据:" << recvStr;// 回发数据给客户端QByteArray replyData = QString("[服务器已接收] %1").arg(recvStr).toUtf8();clientSocket->write(replyData);}
};#endif // SERVERTCP_H
servertcp.cpp
#include "servertcp.h"// 无需额外实现,所有逻辑在头文件的构造函数/槽函数中
主函数main.cpp
#include <QApplication>
#include "servertcp.h"
#include "clienttcp.h"
#include <QThread>
int main(int argc, char *argv[])
{QApplication a(argc, argv);// 启动TCP服务器(后台运行,建议放子线程,这里简化)ServerTcp tcpServer;return a.exec();
}
TCP 客户端 UI 设计(clienttcp.ui)

用 Qt Designer 创建界面,布局如下:
| 控件类型 | 对象名 | 文本 / 提示 | 作用 |
|---|---|---|---|
| QLineEdit | ipEdit | 127.0.0.1 | 输入服务器 IP |
| QLineEdit | portEdit | 9999 | 输入服务器端口 |
| QPushButton | connectBtn | 连接服务器 | 触发连接操作 |
| QTextEdit | msgInputEdit | (空) | 输入要发送的消息 |
| QPushButton | sendBtn | 发送消息 | 触发发送操作 |
| QTextEdit | msgDisplayEdit | (空) | 显示接收 / 发送的消息 |
| QWidget | (主窗口) | TCP 客户端 | 窗口标题 |
UI 结构建议:
┌─────────────────────────────────────┐
│ IP: [127.0.0.1] 端口: [9999] [连接] │
│ ┌─────────────────────────────────┐ │
│ │ │ │
│ │ msgDisplayEdit │ │
│ │ │ │
│ └─────────────────────────────────┘ │
│ [msgInputEdit] [发送] │
└─────────────────────────────────────┘
TCP 客户端代码
clienttcp.h
#ifndef CLIENTTCP_H
#define CLIENTTCP_H
#include <QWidget>
#include <QTcpSocket>
#include <QAbstractSocket>
#include <QDateTime>// 包含UI头文件(Qt Designer生成)
namespace Ui {
class ClientTcp;
}class ClientTcp : public QWidget{Q_OBJECTpublic:explicit ClientTcp(QWidget *parent = nullptr);~ClientTcp(); // 析构函数释放UI和套接字private slots:// 连接按钮点击槽函数void on_connectBtn_clicked();// 发送按钮点击槽函数void on_sendBtn_clicked();// 处理服务器连接成功void onConnected();// 处理服务器断开连接void onDisconnected();// 处理接收服务器数据void onReadyRead();// 处理套接字错误void onErrorOccurred(QAbstractSocket::SocketError err);private:Ui::ClientTcp *ui; // UI对象QTcpSocket m_socket; // TCP客户端套接字// 辅助函数:添加消息到显示框void addMsgToDisplay(const QString &msg, bool isSend = false);
};#endif // CLIENTTCP_H
clienttcp.cpp
#include "clienttcp.h"
#include "ui_clienttcp.h"#include <QHostAddress>ClientTcp::ClientTcp(QWidget *parent): QWidget(parent), ui(new Ui::ClientTcp)
{ui->setupUi(this); // 初始化UI// 禁用发送按钮(未连接时不可用)ui->sendBtn->setEnabled(false);// 关联套接字信号connect(&m_socket, &QTcpSocket::connected, this, &ClientTcp::onConnected);connect(&m_socket, &QTcpSocket::disconnected, this, &ClientTcp::onDisconnected);connect(&m_socket, &QTcpSocket::readyRead, this, &ClientTcp::onReadyRead);connect(&m_socket, static_cast<void (QTcpSocket::*)(QAbstractSocket::SocketError)>(&QTcpSocket::error), this, &ClientTcp::onErrorOccurred);
}ClientTcp::~ClientTcp()
{// 断开连接并释放UIif (m_socket.state() == QTcpSocket::ConnectedState) {m_socket.disconnectFromHost();}delete ui;
}// 连接按钮点击
void ClientTcp::on_connectBtn_clicked()
{// 读取IP和端口QString ip = ui->ipEdit->text().trimmed();QString portStr = ui->portEdit->text().trimmed();bool portOk;quint16 port = portStr.toUShort(&portOk);// 校验输入if (ip.isEmpty() || !portOk || port < 1 || port > 65535) {addMsgToDisplay("[错误] IP或端口输入无效!");return;}// 如果已连接,先断开if (m_socket.state() == QTcpSocket::ConnectedState) {m_socket.disconnectFromHost();ui->connectBtn->setText("连接服务器");ui->sendBtn->setEnabled(false);addMsgToDisplay("[提示] 已断开与服务器的连接");return;}// 连接服务器(异步)m_socket.connectToHost(ip, port);addMsgToDisplay(QString("[提示] 正在连接 %1:%2...").arg(ip).arg(port));
}// 发送按钮点击
void ClientTcp::on_sendBtn_clicked()
{// 读取输入框消息QString msg = ui->msgInputEdit->toPlainText().trimmed();if (msg.isEmpty()) {addMsgToDisplay("[错误] 发送消息不能为空!");return;}// 发送消息QByteArray sendData = msg.toUtf8();qint64 sendLen = m_socket.write(sendData);if (sendLen > 0) {addMsgToDisplay(msg, true); // 标记为发送的消息ui->msgInputEdit->clear(); // 清空输入框} else {addMsgToDisplay("[错误] 消息发送失败:" + m_socket.errorString());}
}// 连接成功处理
void ClientTcp::onConnected()
{ui->connectBtn->setText("断开连接");ui->sendBtn->setEnabled(true); // 启用发送按钮addMsgToDisplay("[成功] 已连接到服务器:" + m_socket.peerAddress().toString() + ":" + QString::number(m_socket.peerPort()));
}// 断开连接处理
void ClientTcp::onDisconnected()
{ui->connectBtn->setText("连接服务器");ui->sendBtn->setEnabled(false); // 禁用发送按钮addMsgToDisplay("[提示] 与服务器断开连接");
}// 接收服务器数据
void ClientTcp::onReadyRead()
{QByteArray recvData = m_socket.readAll();QString recvMsg = QString::fromUtf8(recvData).trimmed();addMsgToDisplay(recvMsg); // 标记为接收的消息
}// 套接字错误处理
void ClientTcp::onErrorOccurred(QAbstractSocket::SocketError err)
{QString errMsg;switch (err) {case QAbstractSocket::ConnectionRefusedError: errMsg = "连接被拒绝(服务器未启动/端口错误)"; break;case QAbstractSocket::HostNotFoundError: errMsg = "主机未找到(IP地址错误)"; break;case QAbstractSocket::NetworkError: errMsg = "网络错误(网络断开)"; break;case QAbstractSocket::SocketTimeoutError: errMsg = "连接超时"; break;default: errMsg = m_socket.errorString();}addMsgToDisplay("[错误] " + errMsg);
}// 辅助函数:添加消息到显示框(带时间戳,区分发送/接收)
void ClientTcp::addMsgToDisplay(const QString &msg, bool isSend)
{QString time = QDateTime::currentDateTime().toString("HH:mm:ss");QString displayMsg;if (isSend) {displayMsg = QString("[%1] 我:%2").arg(time).arg(msg);} else {displayMsg = QString("[%1] 服务器:%2").arg(time).arg(msg);}// 追加消息到显示框(自动换行)ui->msgDisplayEdit->append(displayMsg);
}
主函数(main.cpp)
#include <QApplication>
#include "servertcp.h"
#include "clienttcp.h"
#include <QThread>
int main(int argc, char *argv[])
{QApplication a(argc, argv);// 启动TCP客户端UIClientTcp clientWindow;// clientWindow.setWindowTitle("TCP客户端");// clientWindow.resize(500, 400); // 设置窗口大小clientWindow.show();return a.exec();
}

TCP编程注意事项
- 粘包问题:TCP是字节流,接收方可能将多次发送的小数据合并,需自定义协议解决(如在数据前加长度头、用换行符分隔);
- 连接状态:发送数据前必须检查
QTcpSocket::state()是否为ConnectedState,避免发送失败; - 资源释放:客户端断开后,需调用
QTcpSocket::deleteLater()释放资源,避免内存泄漏; - 多客户端并发:单线程服务器仅能处理一个客户端的请求,需结合
QThread或QtConcurrent实现多客户端并发; - 异常断开:需监听
disconnected()和errorOccurred()信号,处理网络异常断开的情况(如重连)。
UDP与TCP核心区别总结
| 特性 | UDP | TCP |
|---|---|---|
| 连接性 | 无连接(无需握手) | 面向连接(三次握手建立连接) |
| 可靠性 | 不可靠(无确认、重传) | 可靠(确认、重传、排序) |
| 数据格式 | 数据报(有边界) | 字节流(无边界) |
| 传输速度 | 快(协议开销小) | 慢(协议开销大,拥塞控制) |
| 广播/组播 | 支持 | 不支持 |
| 编程复杂度 | 简单(无需处理连接) | 复杂(处理连接、粘包等) |
| 适用场景 | 实时性优先(音视频、游戏) | 可靠性优先(文件、网页) |