解码Qt网络编程(UDP+TCP)
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; // 服务器IP
quint16 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 widgets
CONFIG += 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_OBJECT
public:
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()
{
// 断开连接并释放UI
if (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客户端UI
ClientTcp 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 |
|---|---|---|
| 连接性 | 无连接(无需握手) | 面向连接(三次握手建立连接) |
| 可靠性 | 不可靠(无确认、重传) | 可靠(确认、重传、排序) |
| 数据格式 | 数据报(有边界) | 字节流(无边界) |
| 传输速度 | 快(协议开销小) | 慢(协议开销大,拥塞控制) |
| 广播/组播 | 支持 | 不支持 |
| 编程复杂度 | 简单(无需处理连接) | 复杂(处理连接、粘包等) |
| 适用场景 | 实时性优先(音视频、游戏) | 可靠性优先(文件、网页) |

浙公网安备 33010602011771号