一、UDP 通信概述
UDP(用户数据报协议)是一个轻量级的、面向数据报的、无连接的传输层协议。与TCP不同,UDP不保证数据的可靠传输,不提供报文到达确认、排序以及流量控制等功能。数据包可能会丢失、重复或乱序,但正因为没有建立连接的开销和重传机制,UDP的传输速度非常快。
在Qt中,UDP通信的服务器端和客户端关系是对等的 ------两者都使用同一个QUdpSocket类,不需要像TCP那样区分监听和连接。任何一端都可以发送和接收数据,唯一的区别在于:如果需要接收数据,必须调用bind()绑定一个端口。
UDP支持三种通信模式:
-
单播(Unicast) :一对一通信,发送方需要明确指定接收方的IP和端口
-
广播(Broadcast) :一对所有,向同一局域网内的所有主机发送数据
-
组播(Multicast) :一对一组,只有加入特定组播组的成员才能收到数据
二、QUdpSocket 核心类介绍
QUdpSocket是QAbstractSocket的子类,提供了在UDP协议下发送和接收数据报的能力。
核心方法:
| 方法 | 说明 |
|---|---|
bind(port) |
绑定端口,用于接收数据 |
writeDatagram(data, address, port) |
发送UDP数据报 |
readDatagram(data, size, &sender, &senderPort) |
读取UDP数据报 |
hasPendingDatagrams() |
检查是否有待处理的数据报 |
pendingDatagramSize() |
获取待处理数据报的大小 |
核心信号:
| 信号 | 说明 |
|---|---|
readyRead() |
当有数据到达并准备就绪时触发 |
注意 :使用
QUdpSocket前,需要在项目的.pro文件中添加network模块:
cppQT += network
三、UDP 通信完整流程
1. 创建套接字对象
首先需要创建一个QUdpSocket实例:
cpp
QUdpSocket *udpSocket = new QUdpSocket(this);
2. 绑定端口(接收数据必需)
如果需要接收数据,必须调用bind()将套接字绑定到一个端口:
cpp
bool ret = udpSocket->bind(QHostAddress::Any, 8888);
if (!ret) {
qDebug() << "绑定失败:" << udpSocket->errorString();
}
-
QHostAddress::Any表示监听所有可用的网络接口 -
也可以指定具体的IP地址,如
QHostAddress("192.168.1.100")
3. 发送数据:writeDatagram()
发送数据使用writeDatagram()方法,需要指定数据内容、目标IP地址和目标端口:
cpp
QByteArray datagram = "Hello, UDP!";
udpSocket->writeDatagram(datagram.data(), datagram.size(),
QHostAddress("127.0.0.1"), 8888);
发送广播 :目标地址使用QHostAddress::Broadcast:
cpp
udpSocket->writeDatagram(datagram, QHostAddress::Broadcast, 8888);
发送组播:目标地址使用D类IP地址(224.0.0.0 ~ 239.255.255.255):
cpp
udpSocket->writeDatagram(datagram, QHostAddress("224.0.0.1"), 8888);
4. 接收数据:readDatagram()
接收数据需要先连接readyRead信号到自定义槽函数:
cpp
connect(udpSocket, &QUdpSocket::readyRead,
this, &MyClass::readPendingDatagrams);
在槽函数中,使用readDatagram()读取数据:
cpp
void MyClass::readPendingDatagrams() {
while (udpSocket->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
udpSocket->readDatagram(datagram.data(), datagram.size(),
&sender, &senderPort);
// 处理接收到的数据
qDebug() << "收到来自" << sender.toString()
<< ":" << senderPort
<< "的消息:" << datagram;
}
}
四、完整代码示例
下面是一个完整的UDP通信示例,包含发送端和接收端在同一程序中的实现。
头文件 (udpwidget.h)
cpp
#ifndef UDPWIDGET_H
#define UDPWIDGET_H
#include <QWidget>
#include <QUdpSocket>
#include <QPushButton>
#include <QLineEdit>
#include <QTextEdit>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
class UdpWidget : public QWidget
{
Q_OBJECT
public:
explicit UdpWidget(QWidget *parent = nullptr);
~UdpWidget();
private slots:
void readPendingDatagrams(); // 接收数据槽函数
void onSendButtonClicked(); // 发送按钮点击槽函数
private:
QUdpSocket *udpSocket;
QLineEdit *ipEdit; // 目标IP输入框
QLineEdit *portEdit; // 目标端口输入框
QLineEdit *msgEdit; // 消息输入框
QPushButton *sendBtn; // 发送按钮
QTextEdit *recvEdit; // 接收显示区域
quint16 localPort; // 本地绑定端口
};
#endif // UDPWIDGET_H
源文件 (udpwidget.cpp)
cpp
#include "udpwidget.h"
#include <QMessageBox>
UdpWidget::UdpWidget(QWidget *parent)
: QWidget(parent), localPort(8888)
{
// 设置窗口标题
this->setWindowTitle("Qt UDP 通信示例");
// 创建UI控件
QLabel *ipLabel = new QLabel("目标IP:");
QLabel *portLabel = new QLabel("目标端口:");
QLabel *msgLabel = new QLabel("发送消息:");
ipEdit = new QLineEdit("127.0.0.1");
portEdit = new QLineEdit("8888");
msgEdit = new QLineEdit();
sendBtn = new QPushButton("发送");
recvEdit = new QTextEdit();
recvEdit->setReadOnly(true);
// 布局
QHBoxLayout *topLayout = new QHBoxLayout();
topLayout->addWidget(ipLabel);
topLayout->addWidget(ipEdit);
topLayout->addWidget(portLabel);
topLayout->addWidget(portEdit);
QHBoxLayout *midLayout = new QHBoxLayout();
midLayout->addWidget(msgLabel);
midLayout->addWidget(msgEdit);
midLayout->addWidget(sendBtn);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addLayout(topLayout);
mainLayout->addLayout(midLayout);
mainLayout->addWidget(new QLabel("接收消息:"));
mainLayout->addWidget(recvEdit);
// ============ 第1步:创建套接字对象 ============
udpSocket = new QUdpSocket(this);
// ============ 第2步:绑定端口(接收数据必需) ============
bool ret = udpSocket->bind(QHostAddress::Any, localPort);
if (!ret) {
QMessageBox::critical(this, "错误",
"端口绑定失败:" + udpSocket->errorString());
return;
}
recvEdit->append("✅ 已绑定端口 " + QString::number(localPort) +
",等待接收数据...");
// ============ 连接信号槽(接收数据) ============
connect(udpSocket, &QUdpSocket::readyRead,
this, &UdpWidget::readPendingDatagrams);
// ============ 发送按钮信号槽 ============
connect(sendBtn, &QPushButton::clicked,
this, &UdpWidget::onSendButtonClicked);
}
UdpWidget::~UdpWidget()
{
delete udpSocket;
}
// ============ 第3步:发送数据 ============
void UdpWidget::onSendButtonClicked()
{
QString msg = msgEdit->text().trimmed();
if (msg.isEmpty()) {
QMessageBox::warning(this, "提示", "请输入要发送的消息");
return;
}
QHostAddress targetIP(ipEdit->text());
quint16 targetPort = portEdit->text().toUShort();
QByteArray datagram = msg.toUtf8();
// 使用 writeDatagram 发送数据
qint64 bytesWritten = udpSocket->writeDatagram(
datagram.data(),
datagram.size(),
targetIP,
targetPort
);
if (bytesWritten == -1) {
recvEdit->append("❌ 发送失败:" + udpSocket->errorString());
} else {
recvEdit->append("📤 发送 [" + msg + "] 到 " +
targetIP.toString() + ":" +
QString::number(targetPort));
}
}
// ============ 第4步:接收数据 ============
void UdpWidget::readPendingDatagrams()
{
// 循环读取所有待处理的数据报
while (udpSocket->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
// 使用 readDatagram 读取数据
qint64 bytesRead = udpSocket->readDatagram(
datagram.data(),
datagram.size(),
&sender,
&senderPort
);
if (bytesRead != -1) {
QString msg = QString::fromUtf8(datagram);
recvEdit->append("📥 收到来自 " + sender.toString() +
":" + QString::number(senderPort) +
" 的消息: " + msg);
}
}
}
主函数 (main.cpp)
cpp
#include <QApplication>
#include "udpwidget.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
UdpWidget widget;
widget.resize(600, 400);
widget.show();
return app.exec();
}
五、运行说明
-
启动程序:运行程序后,会自动绑定本地端口8888,可以接收发往该端口的数据
-
发送数据:在"目标IP"和"目标端口"中输入接收方的地址,在"发送消息"中输入内容,点击"发送"按钮
-
接收数据:当其他UDP程序向本机的8888端口发送数据时,会自动接收并显示
测试方法:
-
可以同时启动两个程序实例,将双方的IP和端口互相设置为对方,实现双向通信
-
也可以使用命令行工具(如
nc -u)或Wireshark进行测试
六、总结
Qt下UDP通信的核心流程非常简洁:
| 步骤 | 操作 | 关键代码 |
|---|---|---|
| 1 | 创建套接字 | new QUdpSocket(this) |
| 2 | 绑定端口(接收数据必需) | udpSocket->bind(port) |
| 3 | 发送数据 | udpSocket->writeDatagram(data, addr, port) |
| 4 | 接收数据 | 连接readyRead信号 → readDatagram() |
UDP通信的特点:
-
服务器端和客户端对等,使用相同的类和方法
-
不保证数据可靠到达,适合实时性要求高的场景
-
支持单播、广播、组播三种模式
-
无需建立连接,发送开销小,速度快
在实际开发中,如果需要保证数据可靠性,可以在应用层自行实现确认和重传机制。