65、UDP协议(拓展选学)---------网络编程
UDP协议(拓展选学)
UDP协议
●UDP(用户数据报协议):无连接,提供尽力而为的包传输,适用于对实时性要求高但对数据可靠性要求相对较低的应用。
1.2 UDP协议概述
UDP是一种简单的传输层协议,具有以下特点:
●无连接:发送数据前不需要建立连接,直接发送数据包。
●不保证可靠性:数据包可能丢失、重复或失序,且不进行重传。
●低开销:由于没有连接管理和流量控制,适合需要高性能的应用。
●数据报传输:以单个数据报的形式发送,每个数据报独立处理。
应用场景:
●实时视频/音频传输
●DNS查询
●物联网设备通信
1.3 Qt网络模块中的UDP支持
Qt通过QUdpSocket类提供对UDP通信的支持,使得开发跨平台的UDP应用更加简便。
QUdpSocket类详解
QUdpSocket是Qt中专门用于UDP网络通信的类。它继承自QAbstractSocket,提供了一系列用于发送和接收UDP数据报的方法和信号。
1 QUdpSocket的基本功能
- 发送数据:通过
writeDatagram()方法发送数据报。 - 接收数据:通过绑定套接字并监听数据到达,接收到的数据通过信号
readyRead()通知。 - 绑定端口:通过
bind()方法绑定本地端口进行接收。
2 关键方法解析
void bind(const QHostAddress &address = QHostAddress::Any, quint16 port = 0, BindMode mode = DefaultForPlatform)
-
- 绑定本地地址和端口,以便接收数据。
qint64 writeDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port)
-
- 发送数据报到指定的主机和端口。
qint64 writeDatagram(const char *data, qint64 size, const QHostAddress &host, quint16 port)
-
- 重载形式,发送指定大小的数据。
void close()
-
- 关闭套接字。
3 QUdpSocket的信号
void readyRead()
-
- 当有新的数据报到达时发出。
void datagramWritten(qint64 bytes)
-
- 当数据报被成功发送时发出。
void errorOccurred(QAbstractSocket::SocketError socketError)
-
- 当发生错误时发出。
4 QUdpSocket与TCP编程的对比
|
特性 |
TCP |
UDP |
|
连接性 |
面向连接,需建立连接 |
无连接,直接发送数据报 |
|
可靠性 |
高,保证数据按序传输,无数据丢失 |
低,数据包可能丢失、重复、无序 |
|
传输方式 |
字节流 |
数据报 |
|
开销 |
高,需管理连接和状态 |
低,无需连接管理 |
|
适用场景 |
文件传输、网页浏览、电子邮件,在线游戏等 |
实时视频、音频等 |
聊天室案例UDP版(课外拓展)
UDP应用的场景有限,大多是对数据安全性不高的场景,我们的聊天服务也可以改为UDP版本, 效果如下图

客户端实现
我们可以自定义一个UdpClient类
#ifndef UDPCLIENT_H
#define UDPCLIENT_H
#include <QObject>
#include <QUdpSocket>
class UdpClient : public QObject
{
Q_OBJECT
public:
static UdpClient& Inst(){
static UdpClient udpclient;
return udpclient;
}
void bindPort(QString serverip, quint16 serverport, quint16 selfport);
void processPendingDatagrams();
void setName(QString);
private:
explicit UdpClient(QObject *parent = nullptr);
UdpClient(const UdpClient& uc) = delete;
UdpClient& operator=(const UdpClient&) = delete;
~UdpClient();
//udp socket用来通信
QUdpSocket * _udpSocket;
//服务器地址
QHostAddress _server_address;
//服务器端口
quint16 _server_port;
//客户端用户名
QString _name;
signals:
//通知MainWindow显示信息
void sig_append_msg(QString);
public slots:
//发送消息的槽函数
void slot_send_msg(QString);
};
#endif // UDPCLIENT_H
具体实现
#include "udpclient.h"
#include <QUdpSocket>
#include <QTextStream>
UdpClient::UdpClient(QObject *parent) : QObject(parent),_server_address(QHostAddress("127.0.0.1")),
_server_port(20001)
{
_udpSocket = new QUdpSocket(this);
//连接信息到来的信号
connect(_udpSocket, &QUdpSocket::readyRead, this, &UdpClient::processPendingDatagrams);
}
UdpClient::~UdpClient()
{
}
void UdpClient::slot_send_msg(QString message)
{
message = QString("[%1] : %2").arg(_name).arg(message);
//发送消息
_udpSocket->writeDatagram(message.toUtf8(),
_server_address,
_server_port);
//通知界面显示
emit sig_append_msg(message);
}
//接受数据
void UdpClient::bindPort(QString serverip, quint16 serverport, quint16 selfport)
{
//服务地址
_server_address = QHostAddress(serverip);
//服务器端口
_server_port = serverport;
// 绑定地址和端口
_udpSocket->bind(QHostAddress::Any,selfport,QAbstractSocket::ShareAddress);
}
void UdpClient::processPendingDatagrams()
{
while (_udpSocket->hasPendingDatagrams()) {
QByteArray datagram;
//udp不需要tlv,可以直接读取包的大小
datagram.resize(static_cast<int>(_udpSocket->pendingDatagramSize()));
QHostAddress sender;
quint16 senderPort;
//读取对端发送的数据,并且获取对端的ip和端口
_udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
QString message = QString::fromUtf8(datagram);
qDebug() << "收到来自" << sender.toString() << ":" << senderPort << "的数据:" << message;
//发送消息给MainWindow
emit sig_append_msg(message);
}
}
void UdpClient::setName(QString name)
{
_name = name;
}
MainWindow声明
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
public slots:
//客户端端口改变
void slot_client_port_changed(int value);
//服务器端口改变
void slot_server_port_changed(int value);
//客户端地址改变
void slot_server_address_changed();
//追加消息
void slot_append_msg(QString msg);
private slots:
//发送按钮点击槽函数
void on_send_btn_clicked();
signals:
//通知udpclient发送消息的信号
void sig_send_msg(QString);
};
#endif // MAINWINDOW_H
MainWindow界面拷贝之前的TcpClient版本的MainWindow就行了,稍作修改,增加一个自己的端口号,一般不用修改

MainWindow具体实现
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QRandomGenerator>
#include "udpclient.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->setWindowTitle("客户端");
ui->address_ed->setText("127.0.0.1");
ui->name_ed->setText("尼采");
ui->port_spin->setMaximum(25000);
ui->port_spin->setValue(20001);
ui->clientPort->setMaximum(20000);
//设置textEdit文字大小
QFont font = ui->chatEdit->font();
font.setPointSize(16);
ui->chatEdit->setFont(font);
ui->chatEdit->setReadOnly(true);
// 创建一个 QRandomGenerator 实例
QRandomGenerator *generator = QRandomGenerator::global();
int randomInRange = generator->bounded(0, 20000);
ui->clientPort->setValue(randomInRange);
//显示调用一下槽函数用来绑定端口
slot_client_port_changed(randomInRange);
UdpClient::Inst().setName(ui->name_ed->text());
//选择int类型版本的信号
connect(ui->clientPort, QOverload<int>::of(&QSpinBox::valueChanged),
this, &MainWindow::slot_client_port_changed);
//连接int类型版本的信号
connect(ui->port_spin, QOverload<int>::of(&QSpinBox::valueChanged), this,
&MainWindow::slot_server_port_changed);
//连接服务修改
connect(ui->address_ed,&QLineEdit::editingFinished,this,&MainWindow::slot_server_address_changed);
//连接发送数据的信号
connect(this, &MainWindow::sig_send_msg, &UdpClient::Inst(), &UdpClient::slot_send_msg);
//名字编辑
connect(ui->name_ed, &QLineEdit::editingFinished, this, [=](){
UdpClient::Inst().setName(ui->name_ed->text());
});
//发送信号更新界面
connect(&UdpClient::Inst(), &UdpClient::sig_append_msg,
this, &MainWindow::slot_append_msg);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::slot_client_port_changed(int value)
{
UdpClient::Inst().bindPort(ui->address_ed->text(),
static_cast<quint16>(ui->port_spin->value()),
static_cast<quint16>(value));
}
void MainWindow::slot_server_port_changed(int value)
{
UdpClient::Inst().bindPort(ui->address_ed->text(),
static_cast<quint16>(value),
static_cast<quint16>(ui->clientPort->value()));
}
void MainWindow::slot_server_address_changed()
{
UdpClient::Inst().bindPort(ui->address_ed->text(),
static_cast<quint16>(ui->port_spin->value()),
static_cast<quint16>(ui->clientPort->value()));
}
void MainWindow::on_send_btn_clicked()
{
auto message = ui->msg_ed->text();
emit sig_send_msg(message);
}
void MainWindow::slot_append_msg(QString msg){
ui->chatEdit->append(msg);
}
服务器
udpserver类声明
#ifndef UDPSERVER_H
#define UDPSERVER_H
#include <QObject>
#include <QUdpSocket>
#include <QHash>
#include <QHostAddress>
class UdpServer : public QObject
{
Q_OBJECT
public:
static UdpServer& Inst();
//启动服务
void startServer(int port);
//关闭服务
void closeServer();
//广播消息
void broadCastMsg(QString key, QByteArray datagram);
private:
explicit UdpServer(QObject *parent = nullptr);
UdpServer(const UdpServer& ) = delete;
UdpServer& operator=(const UdpServer&) = delete;
QUdpSocket * _udpSocket;
quint16 _listenPort;
struct ClientInfo{
QHostAddress _address;
quint16 _port;
};
//ip端口字符串到具体地址的映射
QHash<QString, ClientInfo> _ip_ports;
signals:
//通知MainWindow显示消息
void sig_append_msg(QString);
public slots:
//处理udp收到的消息
void processPendingDatagrams();
};
#endif // UDPSERVER_H
udpserver的具体实现
#include "udpserver.h"
#include <QMessageBox>
UdpServer &UdpServer::Inst()
{
static UdpServer udpserver;
return udpserver;
}
void UdpServer::startServer(int port)
{
if (!_udpSocket->bind(QHostAddress::Any, static_cast<quint16>(port))) {
QMessageBox::information(nullptr, "提示信息","绑定端口失败!");
return;
}
QMessageBox::information(nullptr,"提示信息",
QString("实时数据UDP服务器已启动,监听端口[%1]").arg(port));
}
void UdpServer::closeServer()
{
_udpSocket->close();
QMessageBox::information(nullptr,"提示信息",
QString("实时数据UDP服务器已关闭"));
}
//广播消息
void UdpServer::broadCastMsg(QString key, QByteArray datagram)
{
for(auto & ip_port : _ip_ports.keys()){
//如果是消息源客户端则不发送
if(ip_port == key){
continue;
}
//获取用户信息
auto client_info = _ip_ports[ip_port];
//发送信息给其他用户
_udpSocket->writeDatagram(datagram, client_info._address, client_info._port);
}
}
UdpServer::UdpServer(QObject *parent) : QObject(parent)
{
//创建udpsocket
_udpSocket = new QUdpSocket(this);
//连接读取信息的信号
connect(_udpSocket, &QUdpSocket::readyRead, this, &UdpServer::processPendingDatagrams);
}
void UdpServer::processPendingDatagrams()
{
//获取对端发送的消息
while (_udpSocket->hasPendingDatagrams()) {
QByteArray datagram;
//重置大小
datagram.resize(static_cast<int>(_udpSocket->pendingDatagramSize()));
QHostAddress sender;
quint16 senderPort;
_udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
QString message = QString::fromUtf8(datagram);
qDebug() << "收到来自" << sender.toString() << ":" << senderPort << "的消息:" << message;
//发送消息给主界面
emit sig_append_msg(message);
//记录来自客户端的地址和端口,拼接成串
auto key = sender.toString()+ QString::number(senderPort);
//客户端地址信息
ClientInfo client_info;
client_info._address = sender;
client_info._port = senderPort;
_ip_ports.insert(key, client_info);
//广播消息
broadCastMsg(key,datagram);
}
}
MainWindow界面稍作修改,因为udpserve只需要绑定端口就行了

mainwindow的声明
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
//点击开始后按钮
void on_startBtn_clicked();
//点击关闭按钮
void on_closeBtn_clicked();
//追加消息到屏幕
void slot_append_msg(QString);
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
MainWindow具体实现
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "udpserver.h"
#include "udpserver.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->setWindowTitle("服务器");
ui->addressEd->setText("0.0.0.0");
ui->portSpin->setMaximum(25000);
ui->portSpin->setValue(20001);
ui->chatEdit->setReadOnly(true);
//设置textEdit文字大小
QFont font = ui->chatEdit->font();
font.setPointSize(16);
ui->chatEdit->setFont(font);
ui->startBtn->setEnabled(true);
ui->closeBtn->setEnabled(false);
connect(&UdpServer::Inst(),&UdpServer::sig_append_msg,
this, &MainWindow::slot_append_msg);
}
void MainWindow::slot_append_msg(QString msg){
ui->chatEdit->append(msg);
}
MainWindow::~MainWindow()
{
delete ui;
}
//启动服务器
void MainWindow::on_startBtn_clicked()
{
//启动服务
UdpServer::Inst().startServer(static_cast<int>(ui->portSpin->value()));
ui->startBtn->setEnabled(false);
ui->closeBtn->setEnabled(true);
}
void MainWindow::on_closeBtn_clicked()
{
//关闭服务
UdpServer::Inst().closeServer();
ui->startBtn->setEnabled(true);
ui->closeBtn->setEnabled(false);
}
此时运行服务器,启动多个客户端,修改为不同的名字,将消息发送给服务器,可以看到服务器收到消息并回复给其他客户端。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)