前言
在 Qt 开发中,除了 QTcpSocket,QUdpSocket 也是实现进程间通信(IPC)或网络通信的重要工具。虽然 UDP(User Datagram Protocol)不像 TCP 那样"可靠",但在特定场景下(如低延迟、广播、实时数据)具有不可替代的优势。
一、UDP 基础知识
1.UDP 是什么?
User Datagram Protocol(用户数据报协议)
无连接、不可靠、基于数据报(Datagram) 的传输层协议。
每个数据包独立发送,不保证顺序、不重传、可能丢失。
2. UDP 用于 IPC 的可行性
和 TCP 一样,UDP 也支持 本地回环通信(127.0.0.1)。
进程 A 向 127.0.0.1:9999 发送 UDP 包,进程 B 在该地址端口监听,即可通信。
由于无需建立连接,启动更快、开销更低。
因此,UDP 可作为轻量级、低延迟的本地 IPC 方案,尤其适合"发即忘"(fire-and-forget)场景。
3.Qt 对 UDP 的封装:QUdpSocket
QUdpSocket(继承自 QAbstractSocket),面向无连接,支持单播、广播、多播
核心方法:
writeDatagram() 发送一个数据报
readDatagram() 接收一个数据报
bind() 绑定本地端口(接收方必需)
信号 readyRead() ------ 有数据报到达时触发
与 QTcpSocket 不同:QUdpSocket 没有"连接"概念,同一个 socket 可向多个目标发包,也可接收来自任意源的数据。
4.UDP 作为 IPC 方式的优缺点
✅ 优点:
极低延迟,无连接建立、无确认、无重传,内核处理快
开销小,头部仅 8 字节(TCP 为 20+ 字节)
天然支持广播/多播,适合一对多通知(如服务发现)
无粘包问题,每个 writeDatagram() 对应一个完整消息
跨平台 & 跨语言,任何支持 UDP 的程序都能互通
❌ 缺点:
不可靠,数据包可能丢失、重复、乱序
无流量控制,高频发送可能导致内核丢包
消息大小限制,单个数据报 ≤ 64KB(实际建议 < 1500 字节,避免 IP 分片)
无内置身份验证,无法知道数据来自哪个"可信进程"(需应用层处理)
调试困难,丢包不易察觉,需日志或抓包确认
5.单播、广播、组播的区别

简单示例:
单播:
cpp
// Qt 单播示例
socket.writeDatagram(data, QHostAddress("192.168.1.100"), 8888);
广播:
cpp
// Qt 广播示例
socket.writeDatagram(data, QHostAddress::Broadcast, 9999);
// 接收端需 bind(QHostAddress::Any, 9999);
组播:
cpp
// Qt 组播发送
socket.writeDatagram(data, QHostAddress("239.255.0.1"), 8888);
// Qt 组播接收(关键!)
socket.bind(QHostAddress::Any, 8888);
socket.joinMulticastGroup(QHostAddress("239.255.0.1"));
二、代码示例
为udp单独设计界面类:
cpp
#ifndef UDPWINDOW_H
#define UDPWINDOW_H
#include <QWidget>
#include <QTextEdit>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QUdpSocket>
#include <QSpinBox>
#include <QDateTime>
// 🌟 新增:端口常量,一目了然!
static const int SERVER_PORT = 8888;
static const int CLIENT_PORT = 8889;
class UDPWindow : public QWidget
{
Q_OBJECT
public:
explicit UDPWindow(const QString &role, QWidget *parent = nullptr);
private slots:
void onSendMessage();
void onPortChanged(int port);
void onReadyRead();
private:
void appendLog(const QString &msg);
void setupUDPSocket();
QString m_role;
int m_port; // 本地绑定端口(服务器用8888,客户端用8889)
QTextEdit *m_logView;
QLineEdit *m_inputEdit;
QPushButton *m_sendButton;
QSpinBox *m_portSpinBox;
QUdpSocket *m_udpSocket;
QString m_targetHost; // 目标主机(固定为127.0.0.1)
int m_targetPort; // 发送目标端口(服务器→客户端8889,客户端→服务器8888)
};
#endif // UDPWINDOW_H
cpp
#include "udpwindow.h"
#include <QLabel>
#include <QMessageBox>
UDPWindow::UDPWindow(const QString &role, QWidget *parent)
: QWidget(parent)
, m_role(role)
, m_udpSocket(nullptr)
, m_targetHost("127.0.0.1")
{
// 🌟 修复1:按角色初始化本地端口和目标端口
if (role == "Server") {
m_port = SERVER_PORT;
m_targetPort = CLIENT_PORT; // 服务器发送目标:客户端的端口
} else if (role == "Client") {
m_port = CLIENT_PORT;
m_targetPort = SERVER_PORT; // 客户端发送目标:服务器的端口
} else {
// 安全处理:如果角色不对,用默认值
m_port = SERVER_PORT;
m_targetPort = CLIENT_PORT;
appendLog("⚠️ 角色无效,使用默认配置(服务器模式)");
}
setWindowTitle("UDP Socket - " + role);
resize(600, 500);
m_logView = new QTextEdit();
m_logView->setReadOnly(true);
m_inputEdit = new QLineEdit();
m_inputEdit->setPlaceholderText("Enter message to send...");
m_sendButton = new QPushButton("Send Message");
QHBoxLayout *portLayout = new QHBoxLayout();
QLabel *portLabel = new QLabel("Port:");
m_portSpinBox = new QSpinBox();
m_portSpinBox->setRange(1024, 65535);
m_portSpinBox->setValue(m_port); // 用初始化的端口
portLayout->addWidget(portLabel);
portLayout->addWidget(m_portSpinBox);
QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addWidget(m_logView);
mainLayout->addLayout(portLayout);
mainLayout->addWidget(m_inputEdit);
mainLayout->addWidget(m_sendButton);
setLayout(mainLayout);
connect(m_sendButton, &QPushButton::clicked, this, &UDPWindow::onSendMessage);
connect(m_portSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &UDPWindow::onPortChanged);
setupUDPSocket();
onPortChanged(m_port); // 初始化绑定
appendLog("UDP " + m_role + " initialized on port " + QString::number(m_port)
+ " (target: " + QString::number(m_targetPort) + ")");
}
void UDPWindow::setupUDPSocket()
{
m_udpSocket = new QUdpSocket(this);
connect(m_udpSocket, &QUdpSocket::readyRead, this, &UDPWindow::onReadyRead);
}
void UDPWindow::onSendMessage()
{
QString msg = m_inputEdit->text();
if (msg.isEmpty()) return;
QByteArray datagram = msg.toUtf8();
qint64 sent = m_udpSocket->writeDatagram(datagram, QHostAddress(m_targetHost), m_targetPort);
if (sent == -1) {
appendLog("❌ Failed to send: " + m_udpSocket->errorString());
} else {
appendLog("✅ Sent to " + m_targetHost + ":" + QString::number(m_targetPort) + " | " + msg);
m_inputEdit->clear();
}
}
void UDPWindow::onPortChanged(int port)
{
m_port = port;
// 关闭当前绑定(如果已绑定)
if (m_udpSocket->state() == QAbstractSocket::BoundState) {
m_udpSocket->close();
}
// 重新绑定到新端口
if (!m_udpSocket->bind(QHostAddress::Any, m_port)) {
// 🌟 修复2:更友好的错误提示
appendLog("⚠️ Failed to bind to port " + QString::number(m_port) + ": " + m_udpSocket->errorString());
// 建议:弹出提示框(可选,这里用日志代替)
QMessageBox::warning(this, "Binding Error", "Could not bind to port " + QString::number(m_port) + ".\nCheck if port is in use.");
} else {
appendLog("📌 UDP socket bound to port " + QString::number(m_port));
}
}
void UDPWindow::onReadyRead()
{
while (m_udpSocket->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(m_udpSocket->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
m_udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
QString msg = QString::fromUtf8(datagram);
appendLog("🔄 Received from " + sender.toString() + ":" + QString::number(senderPort) + " | " + msg);
}
}
void UDPWindow::appendLog(const QString &msg)
{
m_logView->append(QDateTime::currentDateTime().toString("hh:mm:ss") + " | " + msg);
}
运行效果:


这里实现了udp的双向通信,本质上是双端各自绑定不同的本地端口号,然后发送消息的时候,往对方的端口写入消息,因此,其实开启了两个不同的udp socket bind绑定。
三、总结
udp的相关内容就不多赘述了,直接把三种方式拉个对比表格吧!

最后提一下udp的不可靠性,也就是可能会丢包的问题。既然我们需要做通信,丢包导致的消息未送达问题一定是需要考虑的。
在某些场合下,udp的丢包是可以接受的,比如心跳发送、状态发送这些允许轻量丢包的业务。
但有些情况下,udp的丢包可能会导致问题,比如一次性的命令发送,丢包直接就导致命令未响应了。
实际工作中就遇到过这样的问题,我们需要对底下的几十台机子发送命令,比如执行x操作。因为机子都是连接到局域网内同网段的,那我们直接用自定义协议的udp广播就行了,但实际情况出现了个别机子无响应,具体情况是没有接收到消息。这个时候,我们需要对它重新发送一次消息。
怎么做到呢?机子收到消息之后,会主动向服务端也就是电脑发送应答信号。电脑端记录下它们的ip,并
在短暂延时(如1s)后,判断有哪些设备未应答。之后,电脑端给这些未应答的设备单独补发一次命令,也就是单播。如果还是未应答,那就判定掉线即可。
也就是说,需要通过应答交互,来尽可能确保消息送达。