UDP (User Datagram Protocol 即用户数据报协议)是一个轻量级的,不可靠的,面向数据报的无连接协议。
协议数据包丢失会比较严重。但是由于 UDP的特性:它 不属于连接型协议 ,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用 UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
QUdpSocket类提供了一个 UDP套接字。 QUdpSocket是 QAbstractSocket的子类,允许发送和接收 UDP。
使用该类最常见的方法是使用 bind()绑定到一个地址和端口,然后调用 writeDatagram() 和 readDatagram() / receiveDatagram() 来传输数据(注意一般要少于512字节)。
UDP 通信示意图:

UDP 消息传送有三种模式,分别是单播、广播和组播三种模式。

单播:两主机之间端对端通信。
广播:广播一般使用广播地址 255.255.255.255,将消息发送到在同一广播(也就是局域网内同一网段)网络上的每个主机。
组播:组播(多点广播),也称为"多播",将网络中同一业务类型主机进行了逻辑上的分组,进行数据收发的时候其数据仅仅在同一分组中进行,其他的主机没有加入此分组不能收发对应的数据。
注意: 单播一样和多播是允许在广域网即Internet 上进行传输的,而广播仅仅在同一局域
网上才能进行。
gemini:
这是一个非常实用的技术话题。UDP(用户数据报协议)是网络编程的基石之一。
为了让你彻底理解,我将把 UDP 比作现实生活中的场景,然后手把手教你在 Qt 中如何实现。
一、 生动形象讲解:什么是 UDP?
如果说 TCP(传输控制协议) 像是 "打电话":
你必须先拨通对方(建立连接),对方接听后说"喂"(握手),你们才能确认对方在听,按顺序一句句说,没听清会让对方重说(重传),挂断时还要互道再见(断开连接)。虽然可靠,但步骤繁琐,速度慢。
那么 UDP(用户数据报协议) 就像是 "扔纸飞机" 或 "寄明信片":
-
无连接(Connectionless): 你写好一张纸条,折成飞机扔出去。你不需要确定对方是否做好了准备,也不需要先打招呼。
-
不可靠(Unreliable): 纸飞机飞出去后,可能会被风吹跑,可能会掉进水里,也可能被别人截获。UDP 不保证数据一定送达,也不保证不丢失。
-
无序(Unordered): 你扔了第1、2、3架飞机。对方接到的顺序可能是 3、1、2。UDP 不保证顺序。
-
面向报文(Datagram Oriented): 每一架飞机(数据包)都是独立的,界限分明。
-
极速(Fast): 因为省去了打电话的那些繁琐步骤(握手、确认、排序),UDP 的速度非常快。
适用场景: 视频直播(丢几帧画面没关系,卡顿才要命)、在线游戏(位置同步)、DNS 查询。
二、 Qt 中的 UDP 核心知识点
在 Qt 中,处理 UDP 的核心类是 QUdpSocket。它继承自 QAbstractSocket。
1. 核心流程
-
接收端(Receiver): 就像在家里装了一个信箱。
- 你需要 Bind(绑定) 一个端口(Port)。这等于告诉邮递员:"寄给 8888 号信箱的信,都放到我这里。"
-
发送端(Sender): 就像写信人。
- 你不需要绑定端口(系统会随机分配一个),你只需要知道对方的 IP地址 和 端口号,然后把数据发出去即可。
2. 常用函数 (API)
| 函数名 | 作用 | 也就是... |
|---|---|---|
bind(port) |
绑定端口 | "占领这个信箱"。通常用于接收端。 |
writeDatagram(data, ip, port) |
发送数据报 | "把信扔出去"。指定内容、目标IP和目标端口。 |
hasPendingDatagrams() |
检查是否有待处理数据 | "看看信箱里有没有信"。返回 true/false。 |
pendingDatagramSize() |
获取待读数据的大小 | "这封信有多大"。用于准备足够大的缓冲区。 |
readDatagram(buffer, size) |
读取数据 | "把信取出来读"。 |
3. 关键信号 (Signal)
readyRead():这是最重要的一环。当有新的数据包到达你的端口时,QUdpSocket会自动发出这个信号。你需要用槽函数(Slot)连接它,以便一收到数据就立刻处理。
三、 实战:Qt 简单的 UDP 传输示例
我们将编写一个最简单的程序,包含两部分功能:发送 和 接收。为了方便演示,我们可以在同一个程序里既发又收(自己发给自己)。
1. 准备工作
在你的 Qt 项目文件 (.pro) 中,必须添加网络模块:
cpp
QT += core gui network
2. 代码实现
假设我们需要建立一个简单的类 UdpCommunicator。
头文件 (udpcommunicator.h)
cpp
#ifndef UDPCOMMUNICATOR_H
#define UDPCOMMUNICATOR_H
#include <QObject>
#include <QUdpSocket> // 引入UDP套接字类
class UdpCommunicator : public QObject
{
Q_OBJECT
public:
explicit UdpCommunicator(QObject *parent = nullptr);
// 发送消息的函数供外部调用
void sendMessage(const QString &message);
private slots:
// 处理接收到的数据的槽函数
void processPendingDatagrams();
private:
QUdpSocket *m_udpSocket; // UDP套接字指针
quint16 m_port; // 通信端口
};
#endif // UDPCOMMUNICATOR_H
源文件 (udpcommunicator.cpp)
cpp
#include "udpcommunicator.h"
#include <QDebug>
#include <QNetworkDatagram> // Qt5.8+ 推荐使用,或者直接用 readDatagram
UdpCommunicator::UdpCommunicator(QObject *parent)
: QObject(parent)
{
m_udpSocket = new QUdpSocket(this);
m_port = 12345; // 约定一个端口号,发送和接收都要知道这个号码
// 【接收端逻辑】
// 1. 绑定端口。
// QHostAddress::LocalHost 表示只监听本机的消息。
// 如果要接收局域网其他电脑的消息,通常使用 QHostAddress::Any
bool bindResult = m_udpSocket->bind(QHostAddress::LocalHost, m_port);
if(bindResult) {
qDebug() << "UDP Socket 绑定成功,正在监听端口:" << m_port;
} else {
qDebug() << "绑定失败!端口可能被占用。";
}
// 2. 连接信号。当有数据包来时,触发 processPendingDatagrams
connect(m_udpSocket, &QUdpSocket::readyRead,
this, &UdpCommunicator::processPendingDatagrams);
}
// 【发送端逻辑】
void UdpCommunicator::sendMessage(const QString &message)
{
QByteArray data = message.toUtf8(); // 将字符串转换为字节数组
// 发送数据报
// 参数1: 数据内容
// 参数2: 目标IP (这里发给自己)
// 参数3: 目标端口
m_udpSocket->writeDatagram(data, QHostAddress::LocalHost, m_port);
qDebug() << "已发送数据:" << message;
}
// 【接收处理逻辑】
void UdpCommunicator::processPendingDatagrams()
{
// 循环读取,直到没有挂起的数据报
while (m_udpSocket->hasPendingDatagrams()) {
// 传统写法 (兼容性好):
QByteArray buffer;
buffer.resize(m_udpSocket->pendingDatagramSize()); // 调整缓冲区大小
QHostAddress sender;
quint16 senderPort;
// 读取数据,同时获取发送者的IP和端口
m_udpSocket->readDatagram(buffer.data(), buffer.size(),
&sender, &senderPort);
QString msg = QString::fromUtf8(buffer);
qDebug() << "接收到来自" << sender.toString() << ":" << senderPort
<< "的消息:" << msg;
}
}
3. 如何在 main.cpp 中使用
cpp
#include <QCoreApplication>
#include "udpcommunicator.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
UdpCommunicator comm;
// 模拟发送数据
comm.sendMessage("Hello, UDP World!");
comm.sendMessage("Qt 网络编程很简单!");
return a.exec();
}
四、 深入一点:注意事项与常见坑
-
大小限制(MTU):
-
UDP 数据包最好不要超过 512 字节 或 1400 字节。虽然理论上限是 65535 字节,但超过网络层的 MTU(通常 1500 字节)会导致分片,一旦分片中有一个丢失,整个包就废了,效率会大打折扣。
-
形象比喻: 纸飞机折太大不仅飞不远,还容易散架。
-
-
绑定失败:
-
如果你运行了两个该程序的实例,第二个程序会报错"绑定失败"。因为端口(比如 12345)已经被第一个程序占用了。
-
解决方法: 接收端必须由不同的端口监听,或者使用
QUdpSocket::ShareAddress模式(允许多个程序复用端口,常用于组播)。
-
-
单播 vs 广播 vs 组播:
-
单播 (Unicast): 点对点,就像我只扔给你。(
QHostAddress::LocalHost或具体 IP) -
广播 (Broadcast): 一对所有,就像我在广场用大喇叭喊。发送给
QHostAddress::Broadcast。局域网所有人都能收到。 -
组播 (Multicast): 一对多(特定组),就像我们在一个微信群里说话。需要加入组播组 (
joinMulticastGroup)。
-
总结
Qt 使用 UDP 就三板斧:
-
new QUdpSocket。 -
接收端
bind()端口并关联readyRead信号。 -
发送端直接
writeDatagram()。
gpt:
下面以**"生动形象 + 详细原理 + Qt中使用方法 + 示例代码"**的方式,来讲解 UDP 协议 以及 在 Qt 中如何轻松使用它。
🛰 一、UDP 协议是什么?(生动形象版)
想象你在一个操场上大声喊:"小明,你妈妈喊你回家吃饭啦!"
-
你直接喊 → 没有建立对话
-
小明听到就回复 → 有可能听不到
-
你不会确认他是否听到
-
喊一句就完事儿
这个过程就像 UDP(User Datagram Protocol,用户数据报协议)
UDP 的特点(操场喊话版)
-
无连接(Connectionless)
不打招呼,不建立连接,直接喊。
-
不可靠(Unreliable,但并不表示容易坏)
不保证对方一定收到。如果风大、小明离得太远,叫声可能丢。
-
面向数据报(Datagram)
你一句一句地喊,每句话都是独立的。
-
速度快
没有连接,不确认,不重传,自然效率极高。
-
适用于:
-
实时视频/音频
-
游戏同步
-
直播
-
局域网广播、发现服务
-
🧩 二、UDP 在 Qt 中的工具:QUdpSocket
Qt 提供了一个非常简单好用的类:
QUdpSocket
它是 Qt 网络模块中最易上手的类之一。
🛠 三、QUdpSocket 常用方法(快速掌握)
| 功能 | 函数 | 说明 |
|---|---|---|
| 发送数据 | writeDatagram() |
最常用,发出一个 UDP 包 |
| 接收数据 | readDatagram() |
读取一个 UDP 包 |
| 绑定端口 | bind() |
允许 socket 在某端口收包 |
| 判断是否有数据可读 | hasPendingDatagrams() |
是否有未读数据报 |
| 获取数据报大小 | pendingDatagramSize() |
下一个包的长度 |
| 广播 | writeDatagram(..., QHostAddress::Broadcast) |
发广播 |
| 加入组播 | joinMulticastGroup() |
多播使用 |
| 信号 | readyRead |
当有数据到达触发 |
🧠 四、Qt 程序中使用 UDP 的知识点
Qt 中使用 UDP,你需要了解:
1. 端口号(port)
-
UDP 按端口区分应用
-
收数据前要 bind 端口
2. IP 地址(IPv4/IPv6)
类型:
-
本机地址:127.0.0.1
-
局域网:如 192.168.x.x
-
广播地址:255.255.255.255
-
多播地址:224.0.0.0 ~ 239.255.255.255
3. 事件驱动模型(signals/slots)
有数据到来时 Qt 自动发信号 readyRead():
cpp
connect(socket, &QUdpSocket::readyRead, this, &MyClass::onReadyRead);
无须阻塞等待。
💡 五、Qt 简单使用示例(最小可运行)
以下展示最简单的 发送端 和 接收端
📤 1)UDP 发送端(Sender)
cpp
#include <QUdpSocket>
QUdpSocket udp;
void sendMessage()
{
QByteArray data = "Hello UDP!";
udp.writeDatagram(data, QHostAddress("127.0.0.1"), 45454);
}
只需要一行 writeDatagram() 就可以发送消息。
📥 2)UDP 接收端(Receiver)
cpp
#include <QUdpSocket>
QUdpSocket udp;
void init()
{
// 绑定端口,用于接收
udp.bind(45454);
connect(&udp, &QUdpSocket::readyRead, [&](){
while (udp.hasPendingDatagrams())
{
QByteArray buf;
buf.resize(udp.pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
udp.readDatagram(buf.data(), buf.size(), &sender, &senderPort);
qDebug() << "收到消息:" << buf;
qDebug() << "来自:" << sender.toString() << ":" << senderPort;
}
});
}
🔄 六、完整示例:两个窗口互相发送消息
假设两个界面:A 和 B
端口如下:
| 程序 | 端口 |
|---|---|
| A 发送到 B | 45454 |
| B 发送到 A | 45455 |
A 程序代码示例:
cpp
// A.h
QUdpSocket* socket;
// A.cpp
socket = new QUdpSocket(this);
socket->bind(45455); // 用于接收来自 B 的消息
connect(socket, &QUdpSocket::readyRead, this, &A::onReadyRead);
void A::onReadyRead()
{
while (socket->hasPendingDatagrams())
{
QByteArray data;
data.resize(socket->pendingDatagramSize());
socket->readDatagram(data.data(), data.size());
qDebug() << "A 收到:" << data;
}
}
void A::onSendClicked()
{
QByteArray msg = "Hello, I am A!";
socket->writeDatagram(msg, QHostAddress("127.0.0.1"), 45454);
}
B 程序类似,只是端口互换
📡 七、广播示例(局域网广播)
cpp
udp.writeDatagram("Hello LAN!", QHostAddress::Broadcast, 45454);
所有绑定 45454 的设备都会收到。
🎯 八、知识总结
UDP 优点
-
快
-
简单
-
支持广播、多播
-
高实时性
UDP 缺点
-
不可靠(包会丢、乱序)
-
常需要应用层自行补充可靠性
Qt 中使用 QUdpSocket 的关键点
-
用
bind()才能收包 -
用信号
readyRead异步接收 -
writeDatagram()即可发送 -
无需 connect(),即"无连接式通信"
单播与广播代码:
cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 10_udp_unicast_broadcast
* @brief mainwindow.h
* @author Deng Zhimao
* @email 1252699831@qq.com
* @net www.openedv.com
* @date 2021-04-14
*******************************************************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QUdpSocket>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QTextBrowser>
#include <QLabel>
#include <QComboBox>
#include <QSpinBox>
#include <QHostInfo>
#include <QLineEdit>
#include <QNetworkInterface>
#include <QDebug>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* Udp通信套接字 */
QUdpSocket *udpSocket;
/* 按钮 */
QPushButton *pushButton[5];
/* 标签文本 */
QLabel *label[3];
/* 水平容器 */
QWidget *hWidget[3];
/* 水平布局 */
QHBoxLayout *hBoxLayout[3];
/* 垂直容器 */
QWidget *vWidget;
/* 垂直布局 */
QVBoxLayout *vBoxLayout;
/* 文本浏览框 */
QTextBrowser *textBrowser;
/* 用于显示本地ip */
QComboBox *comboBox;
/* 用于选择端口 */
QSpinBox *spinBox[2];
/* 文本输入框 */
QLineEdit *lineEdit;
/* 存储本地的ip列表地址 */
QList<QHostAddress> IPlist;
/* 获取本地的所有ip */
void getLocalHostIP();
private slots:
/* 绑定端口 */
void bindPort();
/* 解绑端口 */
void unbindPort();
/* 清除文本框时的内容 */
void clearTextBrowser();
/* 接收到消息 */
void receiveMessages();
/* 发送消息 */
void sendMessages();
/* 广播消息 */
void sendBroadcastMessages();
/* 连接状态改变槽函数 */
void socketStateChange(QAbstractSocket::SocketState);
};
#endif // MAINWINDOW_H
cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 10_udp_unicast_broadcast
* @brief mainwindow.cpp
* @author Deng Zhimao
* @email 1252699831@qq.com
* @net www.openedv.com
* @date 2021-04-14
*******************************************************************/
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置主窗体的位置与大小 */
this->setGeometry(0, 0, 800, 480);
/* udp套接字 */
udpSocket = new QUdpSocket(this);
/* 绑定端口按钮 */
pushButton[0] = new QPushButton();
/* 解绑端口按钮 */
pushButton[1] = new QPushButton();
/* 清空聊天文本按钮 */
pushButton[2] = new QPushButton();
/* 发送消息按钮 */
pushButton[3] = new QPushButton();
/* 广播消息按钮 */
pushButton[4] = new QPushButton();
/* 水平布局一 */
hBoxLayout[0] = new QHBoxLayout();
/* 水平布局二 */
hBoxLayout[1] = new QHBoxLayout();
/* 水平布局三 */
hBoxLayout[2] = new QHBoxLayout();
/* 水平布局四 */
// hBoxLayout[3] = new QHBoxLayout();
/* 水平容器一 */
hWidget[0] = new QWidget();
/* 水平容器二 */
hWidget[1] = new QWidget();
/* 水平容器三 */
hWidget[2] = new QWidget();
vWidget = new QWidget();
vBoxLayout = new QVBoxLayout();
/* 标签实例化 */
label[0] = new QLabel();
label[1] = new QLabel();
label[2] = new QLabel();
lineEdit = new QLineEdit();
comboBox = new QComboBox();
spinBox[0] = new QSpinBox();
spinBox[1] = new QSpinBox();
textBrowser = new QTextBrowser();
label[0]->setText("目标IP地址:");
label[1]->setText("绑定端口:");
label[2]->setText("目标端口:");
/* 设置标签根据文本文字大小自适应大小 */
label[0]->setSizePolicy(QSizePolicy::Fixed,
QSizePolicy::Fixed);
label[1]->setSizePolicy(QSizePolicy::Fixed,
QSizePolicy::Fixed);
label[2]->setSizePolicy(QSizePolicy::Fixed,
QSizePolicy::Fixed);
/* 设置端口号的范围,注意不要与主机的已使用的端口号冲突 */
spinBox[0]->setRange(10000, 99999);
spinBox[1]->setRange(10000, 99999);
pushButton[0]->setText("绑定端口");
pushButton[1]->setText("解除绑定");
pushButton[2]->setText("清空文本");
pushButton[3]->setText("发送消息");
pushButton[4]->setText("广播消息");
/* 设置停止监听状态不可用 */
pushButton[1]->setEnabled(false);
/* 设置输入框默认的文本 */
lineEdit->setText("你好!");
/* 水平布局一添加内容 */
hBoxLayout[0]->addWidget(pushButton[0]);
hBoxLayout[0]->addWidget(pushButton[1]);
hBoxLayout[0]->addWidget(pushButton[2]);
/* 设置水平容器的布局为水平布局一 */
hWidget[0]->setLayout(hBoxLayout[0]);
hBoxLayout[1]->addWidget(label[0]);
hBoxLayout[1]->addWidget(comboBox);
hBoxLayout[1]->addWidget(label[1]);
hBoxLayout[1]->addWidget(spinBox[0]);
hBoxLayout[1]->addWidget(label[2]);
hBoxLayout[1]->addWidget(spinBox[1]);
/* 设置水平容器的布局为水平布局二 */
hWidget[1]->setLayout(hBoxLayout[1]);
/* 水平布局三添加内容 */
hBoxLayout[2]->addWidget(lineEdit);
hBoxLayout[2]->addWidget(pushButton[3]);
hBoxLayout[2]->addWidget(pushButton[4]);
/* 设置水平容器三的布局为水平布局一 */
hWidget[2]->setLayout(hBoxLayout[2]);
/* 垂直布局添加内容 */
vBoxLayout->addWidget(textBrowser);
vBoxLayout->addWidget(hWidget[1]);
vBoxLayout->addWidget(hWidget[0]);
vBoxLayout->addWidget(hWidget[2]);
/* 设置垂直容器的布局为垂直布局 */
vWidget->setLayout(vBoxLayout);
/* 居中显示 */
setCentralWidget(vWidget);
/* 获取本地ip */
getLocalHostIP();
/* 信号槽连接 */
connect(pushButton[0], SIGNAL(clicked()),
this, SLOT(bindPort()));
connect(pushButton[1], SIGNAL(clicked()),
this, SLOT(unbindPort()));
connect(pushButton[2], SIGNAL(clicked()),
this, SLOT(clearTextBrowser()));
connect(pushButton[3], SIGNAL(clicked()),
this, SLOT(sendMessages()));
connect(pushButton[4], SIGNAL(clicked()),
this, SLOT(sendBroadcastMessages()));
connect(udpSocket, SIGNAL(readyRead()),
this, SLOT(receiveMessages()));
connect(udpSocket,
SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this,
SLOT(socketStateChange(QAbstractSocket::SocketState)));
}
MainWindow::~MainWindow()
{
}
void MainWindow::bindPort()
{
quint16 port = spinBox[0]->value();
/* 绑定端口需要在socket的状态为UnconnectedState */
if (udpSocket->state() != QAbstractSocket::UnconnectedState)
udpSocket->close();
if (udpSocket->bind(port)) {
textBrowser->append("已经成功绑定端口:"
+ QString::number(port));
/* 设置界面中的元素的可用状态 */
pushButton[0]->setEnabled(false);
pushButton[1]->setEnabled(true);
spinBox[1]->setEnabled(false);
}
}
void MainWindow::unbindPort()
{
/* 解绑,不再监听 */
udpSocket->abort();
/* 设置界面中的元素的可用状态 */
pushButton[0]->setEnabled(true);
pushButton[1]->setEnabled(false);
spinBox[1]->setEnabled(true);
}
/* 获取本地IP */
void MainWindow::getLocalHostIP()
{
// /* 获取主机的名称 */
// QString hostName = QHostInfo::localHostName();
// /* 主机的信息 */
// QHostInfo hostInfo = QHostInfo::fromName(hostName);
// /* ip列表,addresses返回ip地址列表,注意主机应能从路由器获取到
// * IP,否则可能返回空的列表(ubuntu用此方法只能获取到环回IP) */
// IPlist = hostInfo.addresses();
// qDebug()<<IPlist<<endl;
// /* 遍历IPlist */
// foreach (QHostAddress ip, IPlist) {
// if (ip.protocol() == QAbstractSocket::IPv4Protocol)
// comboBox->addItem(ip.toString());
// }
/* 获取所有的网络接口,
* QNetworkInterface类提供主机的IP地址和网络接口的列表 */
QList<QNetworkInterface> list
= QNetworkInterface::allInterfaces();
/* 遍历list */
foreach (QNetworkInterface interface, list) {
/* QNetworkAddressEntry类存储IP地址子网掩码和广播地址 */
QList<QNetworkAddressEntry> entryList
= interface.addressEntries();
/* 遍历entryList */
foreach (QNetworkAddressEntry entry, entryList) {
/* 过滤IPv6地址,只留下IPv4 */
if (entry.ip().protocol() ==
QAbstractSocket::IPv4Protocol) {
comboBox->addItem(entry.ip().toString());
/* 添加到IP列表中 */
IPlist<<entry.ip();
}
}
}
}
/* 清除文本浏览框里的内容 */
void MainWindow::clearTextBrowser()
{
/* 清除文本浏览器的内容 */
textBrowser->clear();
}
/* 客户端接收消息 */
void MainWindow::receiveMessages()
{
/* 局部变量,用于获取发送者的IP和端口 */
QHostAddress peerAddr;
quint16 peerPort;
/* 如果有数据已经准备好 */
while (udpSocket->hasPendingDatagrams()) {
/* udpSocket发送的数据报是QByteArray类型的字节数组 */
QByteArray datagram;
/* 重新定义数组的大小 */
datagram.resize(udpSocket->pendingDatagramSize());
/* 读取数据,并获取发送方的IP地址和端口 */
udpSocket->readDatagram(datagram.data(),
datagram.size(),
&peerAddr,
&peerPort);
/* 转为字符串 */
QString str = datagram.data();
/* 显示信息到文本浏览框窗口 */
textBrowser->append("接收来自"
+ peerAddr.toString()
+ ":"
+ QString::number(peerPort)
+ str);
}
}
/* 客户端发送消息 */
void MainWindow::sendMessages()
{
/* 文本浏览框显示发送的信息 */
textBrowser->append("发送:" + lineEdit->text());
/* 要发送的信息,转为QByteArray类型字节数组,数据一般少于512个字节 */
QByteArray data = lineEdit->text().toUtf8();
/* 要发送的目标Ip地址 */
QHostAddress peerAddr = IPlist[comboBox->currentIndex()];
/* 要发送的目标端口号 */
quint16 peerPort = spinBox[1]->value();
/* 发送消息 */
udpSocket->writeDatagram(data, peerAddr, peerPort);
}
void MainWindow::sendBroadcastMessages()
{
/* 文本浏览框显示发送的信息 */
textBrowser->append("发送:" + lineEdit->text());
/* 要发送的信息,转为QByteArray类型字节数组,数据一般少于512个字节 */
QByteArray data = lineEdit->text().toUtf8();
/* 广播地址,一般为255.255.255.255,
* 同一网段内监听目标端口的程序都会接收到消息 */
QHostAddress peerAddr = QHostAddress::Broadcast;
/* 要发送的目标端口号 */
quint16 peerPort = spinBox[1]->text().toInt();
/* 发送消息 */
udpSocket->writeDatagram(data, peerAddr, peerPort);
}
/* socket状态改变 */
void MainWindow::socketStateChange(QAbstractSocket::SocketState state)
{
switch (state) {
case QAbstractSocket::UnconnectedState:
textBrowser->append("scoket状态:UnconnectedState");
break;
case QAbstractSocket::ConnectedState:
textBrowser->append("scoket状态:ConnectedState");
break;
case QAbstractSocket::ConnectingState:
textBrowser->append("scoket状态:ConnectingState");
break;
case QAbstractSocket::HostLookupState:
textBrowser->append("scoket状态:HostLookupState");
break;
case QAbstractSocket::ClosingState:
textBrowser->append("scoket状态:ClosingState");
break;
case QAbstractSocket::ListeningState:
textBrowser->append("scoket状态:ListeningState");
break;
case QAbstractSocket::BoundState:
textBrowser->append("scoket状态:BoundState");
break;
default:
break;
}
}


组播代码:
本例大体流程首先获取本地 IP地址。创建一个 udpSocket套接字, 加入组播前必须绑定本机主机的端口。加入组播使用 joinMulticastGroup,退出组播使用 leaveMulticastGroup。其他收发消息的功能与上一节单播和广播一样。
cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 10_udp_unicast_broadcast
* @brief mainwindow.h
* @author Deng Zhimao
* @email 1252699831@qq.com
* @net www.openedv.com
* @date 2021-04-14
*******************************************************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QUdpSocket>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QTextBrowser>
#include <QLabel>
#include <QComboBox>
#include <QSpinBox>
#include <QHostInfo>
#include <QLineEdit>
#include <QNetworkInterface>
#include <QDebug>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* Udp通信套接字 */
QUdpSocket *udpSocket;
/* 按钮 */
QPushButton *pushButton[4];
/* 标签文本 */
QLabel *label[3];
/* 水平容器 */
QWidget *hWidget[3];
/* 水平布局 */
QHBoxLayout *hBoxLayout[3];
/* 垂直容器 */
QWidget *vWidget;
/* 垂直布局 */
QVBoxLayout *vBoxLayout;
/* 文本浏览框 */
QTextBrowser *textBrowser;
/* 用于显示本地ip */
QComboBox *comboBox[2];
/* 用于选择端口 */
QSpinBox *spinBox;
/* 文本输入框 */
QLineEdit *lineEdit;
/* 存储本地的ip列表地址 */
QList<QHostAddress> IPlist;
/* 获取本地的所有ip */
void getLocalHostIP();
private slots:
/* 加入组播 */
void joinGroup();
/* 退出组播 */
void leaveGroup();
/* 清除文本框时的内容 */
void clearTextBrowser();
/* 接收到消息 */
void receiveMessages();
/* 组播消息 */
void sendMessages();
/* 连接状态改变槽函数 */
void socketStateChange(QAbstractSocket::SocketState);
};
#endif // MAINWINDOW_H
cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName 10_udp_unicast_broadcast
* @brief mainwindow.cpp
* @author Deng Zhimao
* @email 1252699831@qq.com
* @net www.openedv.com
* @date 2021-04-14
*******************************************************************/
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置主窗体的位置与大小 */
this->setGeometry(0, 0, 800, 480);
/* udp套接字 */
udpSocket = new QUdpSocket(this);
/* 参数1是设置IP_MULTICAST_TTL套接字选项允许应用程序主要限制数据包在Internet中的生存时间,
* 并防止其无限期地循环,数据报跨一个路由会减一,默认值为1,表示多播仅适用于本地子网。*/
udpSocket->setSocketOption(QAbstractSocket::MulticastTtlOption, 1);
/* 加入组播按钮 */
pushButton[0] = new QPushButton();
/* 退出组播按钮 */
pushButton[1] = new QPushButton();
/* 清空聊天文本按钮 */
pushButton[2] = new QPushButton();
/* 组播消息按钮 */
pushButton[3] = new QPushButton();
/* 水平布局一 */
hBoxLayout[0] = new QHBoxLayout();
/* 水平布局二 */
hBoxLayout[1] = new QHBoxLayout();
/* 水平布局三 */
hBoxLayout[2] = new QHBoxLayout();
/* 水平布局四 */
hBoxLayout[3] = new QHBoxLayout();
/* 水平容器一 */
hWidget[0] = new QWidget();
/* 水平容器二 */
hWidget[1] = new QWidget();
/* 水平容器三 */
hWidget[2] = new QWidget();
vWidget = new QWidget();
vBoxLayout = new QVBoxLayout();
/* 标签实例化 */
label[0] = new QLabel();
label[1] = new QLabel();
label[2] = new QLabel();
lineEdit = new QLineEdit();
comboBox[0] = new QComboBox();
comboBox[1] = new QComboBox();
spinBox = new QSpinBox();
textBrowser = new QTextBrowser();
label[0]->setText("本地IP地址:");
label[1]->setText("组播地址:");
label[2]->setText("组播端口:");
/* 设置标签根据文本文字大小自适应大小 */
label[0]->setSizePolicy(QSizePolicy::Fixed,
QSizePolicy::Fixed);
label[1]->setSizePolicy(QSizePolicy::Fixed,
QSizePolicy::Fixed);
label[2]->setSizePolicy(QSizePolicy::Fixed,
QSizePolicy::Fixed);
/* 设置端口号的范围,注意不要与主机的已使用的端口号冲突 */
spinBox->setRange(10000, 99999);
pushButton[0]->setText("加入组播");
pushButton[1]->setText("退出组播");
pushButton[2]->setText("清空文本");
pushButton[3]->setText("组播消息");
/* 设置停止监听状态不可用 */
pushButton[1]->setEnabled(false);
/* 设置输入框默认的文本 */
lineEdit->setText("你好!");
/* 默认添加范围内的一个组播地址 */
comboBox[1]->addItem("239.255.255.1");
/* 设置可编辑,用户可自行修改此地址 */
comboBox[1]->setEditable(true);
/* 水平布局一添加内容 */
hBoxLayout[0]->addWidget(pushButton[0]);
hBoxLayout[0]->addWidget(pushButton[1]);
hBoxLayout[0]->addWidget(pushButton[2]);
/* 设置水平容器的布局为水平布局一 */
hWidget[0]->setLayout(hBoxLayout[0]);
hBoxLayout[1]->addWidget(label[0]);
hBoxLayout[1]->addWidget(comboBox[0]);
hBoxLayout[1]->addWidget(label[1]);
hBoxLayout[1]->addWidget(comboBox[1]);
hBoxLayout[1]->addWidget(label[2]);
hBoxLayout[1]->addWidget(spinBox);
/* 设置水平容器的布局为水平布局二 */
hWidget[1]->setLayout(hBoxLayout[1]);
/* 水平布局三添加内容 */
hBoxLayout[2]->addWidget(lineEdit);
hBoxLayout[2]->addWidget(pushButton[3]);
/* 设置水平容器三的布局为水平布局一 */
hWidget[2]->setLayout(hBoxLayout[2]);
/* 垂直布局添加内容 */
vBoxLayout->addWidget(textBrowser);
vBoxLayout->addWidget(hWidget[1]);
vBoxLayout->addWidget(hWidget[0]);
vBoxLayout->addWidget(hWidget[2]);
/* 设置垂直容器的布局为垂直布局 */
vWidget->setLayout(vBoxLayout);
/* 居中显示 */
setCentralWidget(vWidget);
/* 获取本地ip */
getLocalHostIP();
/* 信号槽连接 */
connect(pushButton[0], SIGNAL(clicked()),
this, SLOT(joinGroup()));
connect(pushButton[1], SIGNAL(clicked()),
this, SLOT(leaveGroup()));
connect(pushButton[2], SIGNAL(clicked()),
this, SLOT(clearTextBrowser()));
connect(pushButton[3], SIGNAL(clicked()),
this, SLOT(sendMessages()));
connect(udpSocket, SIGNAL(readyRead()),
this, SLOT(receiveMessages()));
connect(udpSocket,
SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this,
SLOT(socketStateChange(QAbstractSocket::SocketState)));
}
MainWindow::~MainWindow()
{
}
void MainWindow::joinGroup()
{
/* 获取端口 */
quint16 port = spinBox->value();
/* 获取组播地址 */
QHostAddress groupAddr = QHostAddress(comboBox[1]->currentText());
/* 绑定端口需要在socket的状态为UnconnectedState */
if (udpSocket->state() != QAbstractSocket::UnconnectedState)
udpSocket->close();
/* 加入组播前必须先绑定端口 */
if (udpSocket->bind(QHostAddress::AnyIPv4,
port, QUdpSocket::ShareAddress)) {
/* 加入组播组,返回结果给ok变量 */
bool ok = udpSocket->joinMulticastGroup(groupAddr);
textBrowser->append(ok ? "加入组播成功" : "加入组播失败");
textBrowser->append("组播地址IP:"
+ comboBox[1]->currentText());
textBrowser->append("绑定端口:"
+ QString::number(port));
/* 设置界面中的元素的可用状态 */
pushButton[0]->setEnabled(false);
pushButton[1]->setEnabled(true);
comboBox[1]->setEnabled(false);
spinBox->setEnabled(false);
}
}
void MainWindow::leaveGroup()
{
/* 获取组播地址 */
QHostAddress groupAddr = QHostAddress(comboBox[1]->currentText());
/* 退出组播 */
udpSocket->leaveMulticastGroup(groupAddr);
/* 解绑,不再监听 */
udpSocket->abort();
/* 设置界面中的元素的可用状态 */
pushButton[0]->setEnabled(true);
pushButton[1]->setEnabled(false);
comboBox[1]->setEnabled(true);
spinBox->setEnabled(true);
}
/* 获取本地IP */
void MainWindow::getLocalHostIP()
{
// /* 获取主机的名称 */
// QString hostName = QHostInfo::localHostName();
// /* 主机的信息 */
// QHostInfo hostInfo = QHostInfo::fromName(hostName);
// /* ip列表,addresses返回ip地址列表,注意主机应能从路由器获取到
// * IP,否则可能返回空的列表(ubuntu用此方法只能获取到环回IP) */
// IPlist = hostInfo.addresses();
// qDebug()<<IPlist<<endl;
// /* 遍历IPlist */
// foreach (QHostAddress ip, IPlist) {
// if (ip.protocol() == QAbstractSocket::IPv4Protocol)
// comboBox->addItem(ip.toString());
// }
/* 获取所有的网络接口,
* QNetworkInterface类提供主机的IP地址和网络接口的列表 */
QList<QNetworkInterface> list
= QNetworkInterface::allInterfaces();
/* 遍历list */
foreach (QNetworkInterface interface, list) {
/* QNetworkAddressEntry类存储IP地址子网掩码和广播地址 */
QList<QNetworkAddressEntry> entryList
= interface.addressEntries();
/* 遍历entryList */
foreach (QNetworkAddressEntry entry, entryList) {
/* 过滤IPv6地址,只留下IPv4,并且不需要环回IP */
if (entry.ip().protocol() ==
QAbstractSocket::IPv4Protocol &&
! entry.ip().isLoopback()) {
/* 添加本地IP地址到comboBox[0] */
comboBox[0]->addItem(entry.ip().toString());
/* 添加到IP列表中 */
IPlist<<entry.ip();
}
}
}
}
/* 清除文本浏览框里的内容 */
void MainWindow::clearTextBrowser()
{
/* 清除文本浏览器的内容 */
textBrowser->clear();
}
/* 客户端接收消息 */
void MainWindow::receiveMessages()
{
/* 局部变量,用于获取发送者的IP和端口 */
QHostAddress peerAddr;
quint16 peerPort;
/* 如果有数据已经准备好 */
while (udpSocket->hasPendingDatagrams()) {
/* udpSocket发送的数据报是QByteArray类型的字节数组 */
QByteArray datagram;
/* 重新定义数组的大小 */
datagram.resize(udpSocket->pendingDatagramSize());
/* 读取数据,并获取发送方的IP地址和端口 */
udpSocket->readDatagram(datagram.data(),
datagram.size(),
&peerAddr,
&peerPort);
/* 转为字符串 */
QString str = datagram.data();
/* 显示信息到文本浏览框窗口 */
textBrowser->append("接收来自"
+ peerAddr.toString()
+ ":"
+ QString::number(peerPort)
+ str);
}
}
/* 客户端发送消息 */
void MainWindow::sendMessages()
{
/* 文本浏览框显示发送的信息 */
textBrowser->append("发送:" + lineEdit->text());
/* 要发送的信息,转为QByteArray类型字节数组,数据一般少于512个字节 */
QByteArray data = lineEdit->text().toUtf8();
/* 要发送的目标Ip地址 */
QHostAddress groupAddr = QHostAddress(comboBox[1]->currentText());
/* 要发送的目标端口号 */
quint16 groupPort = spinBox->value();
/* 发送消息 */
udpSocket->writeDatagram(data, groupAddr, groupPort);
}
/* socket状态改变 */
void MainWindow::socketStateChange(QAbstractSocket::SocketState state)
{
switch (state) {
case QAbstractSocket::UnconnectedState:
textBrowser->append("scoket状态:UnconnectedState");
break;
case QAbstractSocket::ConnectedState:
textBrowser->append("scoket状态:ConnectedState");
break;
case QAbstractSocket::ConnectingState:
textBrowser->append("scoket状态:ConnectingState");
break;
case QAbstractSocket::HostLookupState:
textBrowser->append("scoket状态:HostLookupState");
break;
case QAbstractSocket::ClosingState:
textBrowser->append("scoket状态:ClosingState");
break;
case QAbstractSocket::ListeningState:
textBrowser->append("scoket状态:ListeningState");
break;
case QAbstractSocket::BoundState:
textBrowser->append("scoket状态:BoundState");
break;
default:
break;
}
}
