Qt UDP 通信详解

一、UDP 通信概述

UDP(用户数据报协议)是一个轻量级的、面向数据报的、无连接的传输层协议。与TCP不同,UDP不保证数据的可靠传输,不提供报文到达确认、排序以及流量控制等功能。数据包可能会丢失、重复或乱序,但正因为没有建立连接的开销和重传机制,UDP的传输速度非常快。

在Qt中,UDP通信的服务器端和客户端关系是对等的 ------两者都使用同一个QUdpSocket类,不需要像TCP那样区分监听和连接。任何一端都可以发送和接收数据,唯一的区别在于:如果需要接收数据,必须调用bind()绑定一个端口

UDP支持三种通信模式:

  • 单播(Unicast) :一对一通信,发送方需要明确指定接收方的IP和端口

  • 广播(Broadcast) :一对所有,向同一局域网内的所有主机发送数据

  • 组播(Multicast) :一对一组,只有加入特定组播组的成员才能收到数据


二、QUdpSocket 核心类介绍

QUdpSocketQAbstractSocket的子类,提供了在UDP协议下发送和接收数据报的能力。

核心方法

方法 说明
bind(port) 绑定端口,用于接收数据
writeDatagram(data, address, port) 发送UDP数据报
readDatagram(data, size, &sender, &senderPort) 读取UDP数据报
hasPendingDatagrams() 检查是否有待处理的数据报
pendingDatagramSize() 获取待处理数据报的大小

核心信号

信号 说明
readyRead() 当有数据到达并准备就绪时触发

注意 :使用QUdpSocket前,需要在项目的.pro文件中添加network模块:

cpp 复制代码
QT += 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();
}

五、运行说明

  1. 启动程序:运行程序后,会自动绑定本地端口8888,可以接收发往该端口的数据

  2. 发送数据:在"目标IP"和"目标端口"中输入接收方的地址,在"发送消息"中输入内容,点击"发送"按钮

  3. 接收数据:当其他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通信的特点

  • 服务器端和客户端对等,使用相同的类和方法

  • 不保证数据可靠到达,适合实时性要求高的场景

  • 支持单播、广播、组播三种模式

  • 无需建立连接,发送开销小,速度快

在实际开发中,如果需要保证数据可靠性,可以在应用层自行实现确认和重传机制。

相关推荐
程与留17 小时前
07_QMainWindow 与应用程序框架——菜单栏、工具栏、状态栏
c++·qt
总有刁民想爱朕ha18 小时前
Python PyQt5图片批量转MP4视频工具:本地离线免费无水印,完整代码
开发语言·python·qt
十五年专注C++开发21 小时前
100w条数据丝滑滚动!Qt Model/View 架构实战(二)
开发语言·c++·qt
skr爱码士21 小时前
06_Qt 常用数据类型与容器:QString、QList、QVector、QMap 的性能与实现分析
开发语言·c++·qt
苏宸啊1 天前
udp服务器聊天版本
udp
xiaoye-duck2 天前
《Linux网络编程》UDP Socket 编程实战:从 Echo 回显、通用字典服务到套接字封装全流程
linux·网络·udp
程与留2 天前
06_Qt 常用数据类型与容器:QString、QList、QVector、QMap 的性能与实现分析
数据结构·qt
zlinear数据采集卡2 天前
数据采集卡从入门到精通(38):上位机开发实战——Python/QT/LabVIEW的技术选型与分层架构
python·单片机·嵌入式硬件·qt·fpga开发·开源·labview
Zguigo2 天前
lesson37-39计算机网络核心概念精讲:从路由器到UDP的实战与备考指南
计算机网络·udp·智能路由器