一、UDP 协议核心特性
-
无连接协议:通信前无需建立连接
-
面向数据报:每次发送/接收独立的数据包
-
轻量高效:头部开销小(仅8字节)
-
不可靠传输:
-
不保证数据包顺序
-
不保证数据包到达
-
无自动重传机制
-
-
适用场景:
-
实时音视频传输
-
DNS 查询
-
网络状态广播
-
游戏状态同步
-
二、Qt UDP 核心类
类名 | 功能说明 |
---|---|
QUdpSocket |
UDP 数据报套接字 |
QHostAddress |
IP 地址封装 |
QNetworkDatagram |
数据报容器(Qt 5.8+) |
三、UDP 发送端实现
cpp
#include <QUdpSocket>
#include <QDebug>
class UdpSender : public QObject {
Q_OBJECT
public:
explicit UdpSender(QObject *parent = nullptr)
: QObject(parent), udpSocket(new QUdpSocket(this)) {
// 设置广播选项
udpSocket->setSocketOption(QAbstractSocket::MulticastTtlOption, 1);
}
// 发送广播消息
void broadcastMessage(const QString &message) {
QByteArray datagram = message.toUtf8();
// 发送到广播地址
qint64 bytesSent = udpSocket->writeDatagram(
datagram,
QHostAddress::Broadcast, // 255.255.255.255
45454
);
if (bytesSent == -1) {
qWarning() << "Failed to send datagram:" << udpSocket->errorString();
} else {
qInfo() << "Broadcasted message:" << message
<< "to" << QHostAddress::Broadcast.toString();
}
}
// 发送到指定地址
void sendTo(const QString &message, const QHostAddress &address, quint16 port) {
QByteArray datagram = message.toUtf8();
udpSocket->writeDatagram(datagram, address, port);
}
private:
QUdpSocket *udpSocket;
};
四、UDP 接收端实现
cpp
#include <QUdpSocket>
#include <QNetworkDatagram>
class UdpReceiver : public QObject {
Q_OBJECT
public:
explicit UdpReceiver(QObject *parent = nullptr)
: QObject(parent), udpSocket(new QUdpSocket(this)) {
// 绑定到端口接收数据
if (!udpSocket->bind(45454, QUdpSocket::ShareAddress)) {
qCritical() << "Bind failed:" << udpSocket->errorString();
return;
}
qInfo() << "Listening on UDP port 45454...";
// 连接数据到达信号
connect(udpSocket, &QUdpSocket::readyRead,
this, &UdpReceiver::processPendingDatagrams);
}
private slots:
void processPendingDatagrams() {
while (udpSocket->hasPendingDatagrams()) {
// 使用QNetworkDatagram获取元数据(Qt 5.8+)
QNetworkDatagram datagram = udpSocket->receiveDatagram();
if (!datagram.isValid()) continue;
QByteArray data = datagram.data();
QHostAddress senderAddress = datagram.senderAddress();
quint16 senderPort = datagram.senderPort();
qInfo() << "Received from" << senderAddress.toString()
<< ":" << senderPort << "=>" << data;
// 示例:处理特定命令
if (data.startsWith("PING")) {
sendResponse(senderAddress, senderPort);
}
}
}
void sendResponse(const QHostAddress &addr, quint16 port) {
udpSocket->writeDatagram("PONG", addr, port);
}
private:
QUdpSocket *udpSocket;
};
五、关键技术解析
- 绑定模式选项
cpp
// 常用绑定选项
udpSocket->bind(port, QUdpSocket::ShareAddress); // 允许多个套接字绑定同一端口
udpSocket->bind(port, QUdpSocket::ReuseAddressHint); // 地址重用
udpSocket->bind(QHostAddress::AnyIPv4); // 监听所有IPv4接口
- 数据报读写方法对比
cpp
// 传统方法(Qt 4/5)
char buffer[1024];
qint64 size = udpSocket->pendingDatagramSize();
udpSocket->readDatagram(buffer, size, &senderAddr, &senderPort);
// 现代方法(Qt 5.8+)
QNetworkDatagram datagram = udpSocket->receiveDatagram();
if (datagram.isValid()) {
QByteArray data = datagram.data();
// 使用datagram.senderAddress()等获取元数据
}
- 广播与组播
cpp
// IPv4 广播
udpSocket->writeDatagram(data, QHostAddress::Broadcast, port);
// IPv6 组播
QHostAddress groupAddress("FF02::1"); // 所有节点组播地址
udpSocket->joinMulticastGroup(groupAddress);
udpSocket->writeDatagram(data, groupAddress, port);
六、高级应用场景
- 实现服务发现协议
cpp
// 服务端广播服务信息
void ServiceDiscoverer::broadcastService() {
QJsonObject serviceInfo{
{"name", "File Server"},
{"type", "_fileserver._tcp"},
{"port", 8080},
{"ip", getLocalIP()}
};
QByteArray datagram = QJsonDocument(serviceInfo).toJson();
udpSocket->writeDatagram(datagram, QHostAddress::Broadcast, 5353);
}
// 客户端监听服务
void ServiceBrowser::startDiscovery() {
udpSocket->bind(5353, QUdpSocket::ShareAddress);
connect(udpSocket, &QUdpSocket::readyRead, [this]() {
while (udpSocket->hasPendingDatagrams()) {
QNetworkDatagram datagram = udpSocket->receiveDatagram();
QJsonObject service = QJsonDocument::fromJson(datagram.data()).object();
emit serviceDiscovered(service);
}
});
}
- 实现简单可靠传输
cpp
// 带序列号的数据报
struct ReliableDatagram {
quint32 sequence;
QByteArray payload;
};
// 发送端
void sendReliable(const QByteArray &data) {
static quint32 seq = 0;
ReliableDatagram dg{++seq, data};
QByteArray datagram;
QDataStream out(&datagram, QIODevice::WriteOnly);
out << dg.sequence << dg.payload;
udpSocket->writeDatagram(datagram, receiverAddr, port);
// 启动重传定时器
QTimer::singleShot(200, [this, seq, datagram]() {
if (!acknowledged.contains(seq)) {
udpSocket->writeDatagram(datagram, receiverAddr, port); // 重传
}
});
}
// 接收端
void processDatagram() {
QNetworkDatagram dg = udpSocket->receiveDatagram();
ReliableDatagram packet;
QDataStream in(dg.data());
in >> packet.sequence >> packet.payload;
// 发送ACK
QByteArray ack;
QDataStream ackOut(&ack, QIODevice::WriteOnly);
ackOut << packet.sequence;
udpSocket->writeDatagram(ack, dg.senderAddress(), dg.senderPort());
// 处理数据
if (packet.sequence > lastSequence) {
processPayload(packet.payload);
lastSequence = packet.sequence;
}
}
七、调试与优化技巧
-
网络诊断命令
查看UDP端口监听
netstat -anu
测试UDP连通性
nc -u <host> <port>
-
性能优化
cpp
// 增大发送缓冲区
udpSocket->setSocketOption(QAbstractSocket::SendBufferSizeSocketOption, 1024 * 1024);
// 禁用拥塞控制 (实时应用)
udpSocket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
// 使用原始套接字 (需要权限)
if (udpSocket->bind(QHostAddress::Any, port, QUdpSocket::DontShareAddress)) {
udpSocket->setSocketOption(QAbstractSocket::SendBufferSizeSocketOption, 0);
}
- 错误处理
cpp
connect(udpSocket, &QAbstractSocket::errorOccurred, [](QAbstractSocket::SocketError error) {
switch(error) {
case QAbstractSocket::AddressInUseError:
qCritical() << "Port already in use";
break;
case QAbstractSocket::DatagramTooLargeError:
qWarning() << "Datagram exceeds MTU size";
break;
case QAbstractSocket::NetworkError:
qWarning() << "Network error occurred";
break;
default:
qWarning() << "UDP error:" << error;
}
});
八、完整示例:局域网设备发现
cpp
// 设备发现广播器
class DeviceBroadcaster : public QObject {
public:
DeviceBroadcaster() {
connect(&timer, &QTimer::timeout, this, &DeviceBroadcaster::broadcast);
timer.start(5000); // 每5秒广播一次
}
private:
void broadcast() {
QJsonObject deviceInfo{
{"name", QSysInfo::machineHostName()},
{"os", QSysInfo::prettyProductName()},
{"ip", getLocalIP()},
{"timestamp", QDateTime::currentSecsSinceEpoch()}
};
QByteArray datagram = QJsonDocument(deviceInfo).toJson();
QUdpSocket socket;
socket.writeDatagram(datagram, QHostAddress::Broadcast, 37020);
}
QString getLocalIP() {
foreach (const QHostAddress &address, QNetworkInterface::allAddresses()) {
if (address.protocol() == QAbstractSocket::IPv4Protocol &&
address != QHostAddress::LocalHost) {
return address.toString();
}
}
return "127.0.0.1";
}
QTimer timer;
};
// 设备发现监听器
class DeviceListener : public QObject {
public:
DeviceListener() {
udpSocket.bind(37020, QUdpSocket::ShareAddress);
connect(&udpSocket, &QUdpSocket::readyRead, this, &DeviceListener::processDatagrams);
}
signals:
void deviceFound(const QJsonObject &deviceInfo);
private:
void processDatagrams() {
while (udpSocket.hasPendingDatagrams()) {
QNetworkDatagram datagram = udpSocket.receiveDatagram();
QJsonObject device = QJsonDocument::fromJson(datagram.data()).object();
// 过滤过期的广播
qint64 now = QDateTime::currentSecsSinceEpoch();
if (now - device["timestamp"].toInt() < 10) {
emit deviceFound(device);
}
}
}
QUdpSocket udpSocket;
};
最佳实践:对于需要可靠传输的场景,可在应用层实现ACK机制和序列号;对于实时流媒体,可结合RTP协议;在公网环境中使用时,需注意NAT穿透问题(可借助STUN/TURN服务器)。