UDP 通信-Qt-思维导图-学习笔记

UDP 通信

在这里插入图片描述

简介

UDP概述

  • UDP(User Datagram Protocol)是一个轻量级、无连接且不可靠的面向数据报的协议

  • 由于其快速响应和资源消耗少,适用于文字聊天、音视频通信等要求快速响应但丢包影响不大的场景

UDP的应用

  • QQ聊天使用UDP发送文字内容,因其用户量大、对实时性要求高,但安全性要求较低

  • 传输文件时,QQ则使用TCP协议以保证传输的可靠性

UDP的优缺点

  • 优点:资源消耗小、处理速度快

  • 缺点:不可靠,在网络质量差的情况下数据包丢失严重

QUdpSocket类

  • QUdpSocket是QAbstractSocket的子类,提供UDP数据报的发送和接收功能

  • 常用方法包括bind()绑定地址和端口、writeDatagram()发送数据、readDatagram()和receiveDatagram()接收数据

数据传输和信号

  • 发送数据通常少于512字节,超过512字节的数据会在IP层被分片

  • 调用connectToHost()可以使用标准的QIODevice函数进行数据传输

  • readyRead()信号在数据报到达时发出,需及时读取数据报

UDP通信模式

  • 单播(unicast):用于两个主机之间的端对端通信,需要指定对方的IP地址和端口

  • 广播(broadcast):发送消息到同一局域网内的所有主机,使用广播地址255.255.255.255

  • 组播(multicast):将同一业务类型的主机进行逻辑分组,只在组内进行数据传输,避免影响其他主机

注意事项

  • 广播仅在局域网内有效,不会被路由器转发

  • 单播和组播可在广域网(Internet)上进行传输,但广播仅限于局域网内

UDP 单播与广播

简述

  • 单播和广播UDP可以通过相同的程序结构实现,区别仅在于发送数据时使用的IP地址不同

  • 在编写代码时,可以根据需要选择单播或广播模式,通过设置不同的目标IP地址来实现

应用实例

  • 大致流程

    • 获取本地 IP 地址

      • 首先需要获取设备的本地 IP 地址,这是进行网络通信的基础,确保设备能够在本地网络中被识别和访问
    • 创建 UDP 套接字

      • 使用 QUdpSocket 类创建一个 UDP 套接字。UDP(用户数据报协议)是一种无连接的传输层协议,适用于需要快速传输而可以容忍少量丢失的数据场景
    • 绑定本地主机的端口

      • 将创建的 UDP 套接字绑定到本地主机的特定端口上,这个端口将用于监听传入的数据报
    • 使用读写函数进行数据交互

      • 通过 QUdpSocket 类提供的 readDatagram 和 writeDatagram 函数,实现数据的接收和发送。这些函数需要知道目标 IP 地址和端口,以确保数据能够正确地发送到指定的目的地
    • 完成消息的接收与发送

      • 利用上述步骤和函数,可以实现消息的接收和发送,确保网络通信的顺利进行
  • 项目文件文件第一行添加的代码部分如下

    • 1 QT += core gui network
  • mainwindow.h

    • 1 #ifndef MAINWINDOW_H
      2 #define MAINWINDOW_H
      3
      4 #include
      5 #include
      6 #include
      7 #include
      8 #include
      9 #include
      10 #include
      11 #include
      12 #include
      13 #include
      14 #include
      15 #include
      16 #include
      17
      18 class MainWindow : public QMainWindow
      19 {
      20 Q_OBJECT
      21
      22 public:
      23 MainWindow(QWidget parent = nullptr);
      24 ~MainWindow();
      25
      26 private:
      27 /
      Udp 通信套接字 */
      28 QUdpSocket udpSocket;
      29
      30 /
      按钮 */
      31 QPushButton pushButton[5];
      32
      33 /
      标签文本 */
      34 QLabel label[3];
      35
      36 /
      水平容器 */
      37 QWidget hWidget[3];
      38
      39 /
      水平布局 */
      40 QHBoxLayout hBoxLayout[3];
      41
      42 /
      垂直容器 */
      43 QWidget vWidget;
      44
      45 /
      垂直布局 */
      46 QVBoxLayout vBoxLayout;
      47
      48 /
      文本浏览框 */
      49 QTextBrowser textBrowser;
      50
      51 /
      用于显示本地 ip */
      52 QComboBox comboBox;
      53
      54 /
      用于选择端口 */
      55 QSpinBox spinBox[2];
      56
      57 /
      文本输入框 */
      58 QLineEdit lineEdit;
      59
      60 /
      存储本地的 ip 列表地址 /
      61 QList IPlist;
      62
      63 /
      获取本地的所有 ip /
      64 void getLocalHostIP();
      65
      66 private slots:
      67 /
      绑定端口 /
      68 void bindPort();
      69
      70 /
      解绑端口 /
      71 void unbindPort();
      72
      73 /
      清除文本框时的内容 /
      74 void clearTextBrowser();
      75
      76 /
      接收到消息 /
      77 void receiveMessages();
      78
      79 /
      发送消息 /
      80 void sendMessages();
      81
      82 /
      广播消息 /
      83 void sendBroadcastMessages();
      84
      85 /
      连接状态改变槽函数 */
      86 void socketStateChange(QAbstractSocket::SocketState);
      87 };
      88 #endif // MAINWINDOW_H

    • 主要是声明界面用的元素,及一些槽函数。重点是声明 udpSocket

  • mainwindow.cpp

1 #include “mainwindow.h”
2
3 MainWindow::MainWindow(QWidget parent)
4 : QMainWindow(parent)
5{
6 /
设置主窗体的位置与大小 /
7 this->setGeometry(0, 0, 800, 480);
8
9 /
udp 套接字 /
10 udpSocket = new QUdpSocket(this);
11
12 /
绑定端口按钮 /
13 pushButton[0] = new QPushButton();
14 /
解绑端口按钮 /
15 pushButton[1] = new QPushButton();
16 /
清空聊天文本按钮 /
17 pushButton[2] = new QPushButton();
18 /
发送消息按钮 /
19 pushButton[3] = new QPushButton();
20 /
广播消息按钮 /
21 pushButton[4] = new QPushButton();
22
23 /
水平布局一 /
24 hBoxLayout[0] = new QHBoxLayout();
25 /
水平布局二 /
26 hBoxLayout[1] = new QHBoxLayout();
27 /
水平布局三 /
28 hBoxLayout[2] = new QHBoxLayout();
29 /
水平布局四 /
30 hBoxLayout[3] = new QHBoxLayout();
31
32 /
水平容器一 /
33 hWidget[0] = new QWidget();
34 /
水平容器二 /
35 hWidget[1] = new QWidget();
36 /
水平容器三 /
37 hWidget[2] = new QWidget();
38
39
40 vWidget = new QWidget();
41 vBoxLayout = new QVBoxLayout();
42
43 /
标签实例化 /
44 label[0] = new QLabel();
45 label[1] = new QLabel();
46 label[2] = new QLabel();
47
48 lineEdit = new QLineEdit();
49 comboBox = new QComboBox();
50 spinBox[0] = new QSpinBox();
51 spinBox[1] = new QSpinBox();
52 textBrowser = new QTextBrowser();
53
54 label[0]->setText(“目标 IP 地址:”);
55 label[1]->setText(“绑定端口:”);
56 label[2]->setText(“目标端口:”);
57
58 /
设置标签根据文本文字大小自适应大小 /
59 label[0]->setSizePolicy(QSizePolicy::Fixed,
60 QSizePolicy::Fixed);
61 label[1]->setSizePolicy(QSizePolicy::Fixed,
62 QSizePolicy::Fixed);
63 label[2]->setSizePolicy(QSizePolicy::Fixed,
64 QSizePolicy::Fixed);
65
66 /
设置端口号的范围,注意不要与主机的已使用的端口号冲突 /
67 spinBox[0]->setRange(10000, 99999);
68 spinBox[1]->setRange(10000, 99999);
69
70 pushButton[0]->setText(“绑定端口”);
71 pushButton[1]->setText(“解除绑定”);
72 pushButton[2]->setText(“清空文本”);
73 pushButton[3]->setText(“发送消息”);
74 pushButton[4]->setText(“广播消息”);
75
76 /
设置停止监听状态不可用 /
77 pushButton[1]->setEnabled(false);
78
79 /
设置输入框默认的文本 /
80 lineEdit->setText(“您好!”);
81
82 /
水平布局一添加内容 /
83 hBoxLayout[0]->addWidget(pushButton[0]);
84 hBoxLayout[0]->addWidget(pushButton[1]);
85 hBoxLayout[0]->addWidget(pushButton[2]);
86
87 /
设置水平容器的布局为水平布局一 /
88 hWidget[0]->setLayout(hBoxLayout[0]);
89
90 hBoxLayout[1]->addWidget(label[0]);
91 hBoxLayout[1]->addWidget(comboBox);
92 hBoxLayout[1]->addWidget(label[1]);
93 hBoxLayout[1]->addWidget(spinBox[0]);
94 hBoxLayout[1]->addWidget(label[2]);
95 hBoxLayout[1]->addWidget(spinBox[1]);
96
97 /
设置水平容器的布局为水平布局二 /
98 hWidget[1]->setLayout(hBoxLayout[1]);
99
100 /
水平布局三添加内容 /
101 hBoxLayout[2]->addWidget(lineEdit);
102 hBoxLayout[2]->addWidget(pushButton[3]);
103 hBoxLayout[2]->addWidget(pushButton[4]);
104
105 /
设置水平容器三的布局为水平布局一 /
106 hWidget[2]->setLayout(hBoxLayout[2]);
107
108 /
垂直布局添加内容 /
109 vBoxLayout->addWidget(textBrowser);
110 vBoxLayout->addWidget(hWidget[1]);
111 vBoxLayout->addWidget(hWidget[0]);
112 vBoxLayout->addWidget(hWidget[2]);
113
114 /
设置垂直容器的布局为垂直布局 /
115 vWidget->setLayout(vBoxLayout);
116
117 /
居中显示 /
118 setCentralWidget(vWidget);
119
120 /
获取本地 ip /
121 getLocalHostIP();
122
123 /
信号槽连接 /
124 connect(pushButton[0], SIGNAL(clicked()),
125 this, SLOT(bindPort()));
126 connect(pushButton[1], SIGNAL(clicked()),
127 this, SLOT(unbindPort()));
128 connect(pushButton[2], SIGNAL(clicked()),
129 this, SLOT(clearTextBrowser()));
130 connect(pushButton[3], SIGNAL(clicked()),
131 this, SLOT(sendMessages()));
132 connect(pushButton[4], SIGNAL(clicked()),
133 this, SLOT(sendBroadcastMessages()));
134 connect(udpSocket, SIGNAL(readyRead()),
135 this, SLOT(receiveMessages()));
136 connect(udpSocket,
137 SIGNAL(stateChanged(QAbstractSocket::SocketState)),
138 this,
139 SLOT(socketStateChange(QAbstractSocket::SocketState)));
140 }
141
142 MainWindow::~MainWindow()
143 {
144 }
145
146 void MainWindow::bindPort()
147 {
148 quint16 port = spinBox[0]->value();
149
150 /
绑定端口需要在 socket 的状态为 UnconnectedState /
151 if (udpSocket->state() != QAbstractSocket::UnconnectedState)
152 udpSocket->close();
153
154 if (udpSocket->bind(port)) {
155 textBrowser->append(“已经成功绑定端口:”
156 + QString::number(port));
157
158 /
设置界面中的元素的可用状态 /
159 pushButton[0]->setEnabled(false);
160 pushButton[1]->setEnabled(true);
161 spinBox[1]->setEnabled(false);
162 }
163 }
164
165 void MainWindow::unbindPort()
166 {
167 /
解绑,不再监听 /
168 udpSocket->abort();
169
170 /
设置界面中的元素的可用状态 /
171 pushButton[0]->setEnabled(true);
172 pushButton[1]->setEnabled(false);
173 spinBox[1]->setEnabled(true);
174 }
175
176 /
获取本地 IP /
177 void MainWindow::getLocalHostIP()
178 {
179 // /
获取主机的名称 /
180 // QString hostName = QHostInfo::localHostName();
181
182 // /
主机的信息 /
183 // QHostInfo hostInfo = QHostInfo::fromName(hostName);
184
185 // /
ip 列表,addresses 返回 ip 地址列表,注意主机应能从路由器获取到
186 // * IP,否则可能返回空的列表(ubuntu 用此方法只能获取到环回 IP) /
187 // IPlist = hostInfo.addresses();
188 // qDebug()<<IPlist<<endl;
189
190 // /
遍历 IPlist /
191 // foreach (QHostAddress ip, IPlist) {
192 // if (ip.protocol() == QAbstractSocket::IPv4Protocol)
193 //
194 // }
195
comboBox->addItem(ip.toString());
196 /
获取所有的网络接口,
197 * QNetworkInterface 类提供主机的 IP 地址和网络接口的列表 /
198 QList list
199 = QNetworkInterface::allInterfaces();
200
201 /
遍历 list /
202 foreach (QNetworkInterface interface, list) {
203
204 /
QNetworkAddressEntry 类存储 IP 地址子网掩码和广播地址 /
205 QList entryList
206 = interface.addressEntries();
207
208 /
遍历 entryList /
209 foreach (QNetworkAddressEntry entry, entryList) {
210 /
过滤 IPv6 地址,只留下 IPv4 /
211 if (entry.ip().protocol() ==
212 QAbstractSocket::IPv4Protocol) {
213 comboBox->addItem(entry.ip().toString());
214 /
添加到 IP 列表中 /
215 IPlist<<entry.ip();
216 }
217 }
218 }
219 }
220
221 /
清除文本浏览框里的内容 /
222 void MainWindow::clearTextBrowser()
223 {
224 /
清除文本浏览器的内容 /
225 textBrowser->clear();
226 }
227
228 /
客户端接收消息 /
229 void MainWindow::receiveMessages()
230 {
231 /
局部变量,用于获取发送者的 IP 和端口 /
232 QHostAddress peerAddr;
233 quint16 peerPort;
234
235 /
如果有数据已经准备好 /
236 while (udpSocket->hasPendingDatagrams()) {
237 /
udpSocket 发送的数据报是 QByteArray 类型的字节数组 /
238 QByteArray datagram;
239
240 /
重新定义数组的大小 /
241 datagram.resize(udpSocket->pendingDatagramSize());
242
243 /
读取数据,并获取发送方的 IP 地址和端口 /
244 udpSocket->readDatagram(datagram.data(),
245 datagram.size(),
246 &peerAddr,
247 &peerPort);
248 /
转为字符串 /
249 QString str = datagram.data();
250
251 /
显示信息到文本浏览框窗口 /
252 textBrowser->append(“接收来自”
253 + peerAddr.toString()
254 + “:”
255 + QString::number(peerPort)
256 + str);
257 }
258 }
259
260 /
客户端发送消息 /
261 void MainWindow::sendMessages()
262 {
263 /
文本浏览框显示发送的信息 /
264 textBrowser->append(“发送:” + lineEdit->text());
265
266 /
要发送的信息,转为 QByteArray 类型字节数组,数据一般少于 512 个字节 /
267 QByteArray data = lineEdit->text().toUtf8();
268
269 /
要发送的目标 Ip 地址 */
270 QHostAddress peerAddr = IPlist[comboBox->currentIndex()];
271
313 case QAbstractSocket::ClosingState:
314 textBrowser->append(“scoket 状态:ClosingState”);
315 break;
316 case QAbstractSocket::ListeningState:
317 textBrowser->append(“scoket 状态:ListeningState”);
318 break;
319 case QAbstractSocket::BoundState:
320 textBrowser->append(“scoket 状态:BoundState”);
321 break;
322 default:
323 break;
324 }
325 }

- 绑定端口(第 146~163 行)

	- 使用 bind 方法来绑定一个端口

	- 需要确保绑定的端口没有与主机上已经使用的端口发生冲突

- 解绑端口(第 165~174 行)

	- 使用 abort 方法来解绑端口

	- 解绑可以解除之前绑定的端口,使其不再监听

- 接收消息(第 229~258 行)

	- 接收消息数据类型为 QByteArray 字节数组

	- 使用 readDatagram 方法来读取接收到的数据

	- 在 readDatagram 方法中,可以获取发送方的套接字 IP 地址和端口号

- 单播消息(第 261~277 行)

	- 单播消息需要知道目标 IP 地址和目标端口号

	- 使用 writeDatagram 方法发送消息到指定的目标 IP 和端口

- 广播消息(第 279~296 行)

	- 与单播消息的区别在于目标 IP 地址为广播地址

	- 一般的广播地址为 255.255.255.255

	- 使用 writeDatagram 方法将消息发送到广播地址

程序运行效果

  • 程序角色与端口绑定

    • 既可以作为发送者也可以作为接收者

    • 在同一台主机上运行两个本例程序时,不能绑定同一个端口,否则会导致冲突

    • 在同一局域网内不同主机上运行此程序时,可以绑定相同的端口号

  • 环回地址测试

    • 设置目标 IP 地址为 127.0.0.1,这是 Ubuntu/Windows 上的环回 IP 地址,适用于无网络时的测试

    • 绑定端口号与目标端口号相同,例如都为 10000,实现自发自收

  • 发送与接收消息

    • 点击发送消息按钮时,文本消息窗口显示发送的数据“您好!”,同时接收到由本地 IP 127.0.0.1 发出的相同数据

    • 通信套接字的标识为 ffff:

    • 选择目标 IP 地址为 127.0.0.1,因此通信必须使用相同网段的 IP 设备,不能用本地环回 IP 发送消息到其他主机上

  • 广播消息

    • 点击广播消息按钮时,目标 IP 地址变为广播地址 255.255.255.255

    • 将收到从本地 IP 地址 192.168.x.x 发送的数据,例如从 192.168.1.129 发送过来的数据

    • 环回 IP 127.0.0.1 的广播地址为 255.0.0.0,因此与 255.255.255.255 网段内的 IP 通信数据必须由 192.168.x.x 发出

    • 同一网段上的其他主机如果正在监听目标端口,它们将同时收到广播消息

UDP 组播

概述

  • 网络通讯方式

    • 传统网络通讯有两种方式:单播(一对一通讯)和广播(一台源主机与网络中所有其他主机通讯)

    • 单播和广播在多点发送时存在缺陷,如广播可能引起带宽浪费和广播风暴,单播则可能导致带宽和源主机负荷的浪费

  • 组播技术的引入

    • 组播技术类似于 QQ 群,只有加入群的用户才能收到消息,解决了单播和广播的缺陷

    • 组播使用 D 类 IP 地址(224.0.0.0~239.255.255.255),这些地址用于多点广播,标识一组主机

  • D 类 IP 地址的使用

    • 224.0.0.0~224.0.0.255 为预留的组播地址,其中 224.0.0.0 保留,其他供路由协议使用

    • 224.0.1.0~238.255.255.255 为用户可用的组播地址,全网范围内有效

    • 239.0.0.0~239.255.255.255 为本地管理组播地址,仅在特定的本地范围内有效

  • 本地网络中的组播地址

    • 在家庭和办公网络局域网内使用 UDP 组播功能时,可用的组播地址范围是 239.0.0.0~239.255.255.255
  • QUdpSocket 类支持

    • QUdpSocket 类支持 UDP 组播,提供了 joinMulticastGroup 和 leaveMulticastGroup 方法来加入和离开多播组

    • 绑定端口、发送接收功能与 UDP 单播和广播相同,只需学会使用 joinMulticastGroup 和 leaveMulticastGroup 方法即可

应用实例

  • 大致流程

    • 获取本地 IP 地址

      • 首先需要获取本地 IP 地址,这一步是初始化网络通讯的基础
    • 创建 udpSocket 套接字

      • 创建一个 udpSocket 套接字,用于进行 UDP 通讯
    • 绑定本机主机的端口

      • 在加入组播之前,必须先绑定本机主机的端口。绑定端口是为了能够接收和发送数据
    • 加入组播

      • 使用 joinMulticastGroup 方法将本地主机加入指定的多播组
    • 退出组播

      • 使用 leaveMulticastGroup 方法将本地主机从多播组中移除
    • 收发消息功能

      • 其他消息的收发功能与上一节的单播和广播相同。可以使用 readDatagram 方法接收数据和 writeDatagram 方法发送数据
  • 项目文件文件第一行添加的代码部分如下

    • 1 QT += core gui network
  • mainwindow.h

    • 165
      66 private slots:
      67 /* 加入组播 /
      68 void joinGroup();
      69
      70 /
      退出组播 /
      71 void leaveGroup();
      72
      73 /
      清除文本框时的内容 /
      74 void clearTextBrowser();
      75
      76 /
      接收到消息 /
      77 void receiveMessages();
      78
      79 /
      组播消息 /
      80 void sendMessages();
      81
      82 /
      连接状态改变槽函数 */
      83 void socketStateChange(QAbstractSocket::SocketState);
      84 };
      85 #endif // MAINWINDOW_H #ifndef MAINWINDOW_H
      2 #define MAINWINDOW_H
      3
      4 #include
      5 #include
      6 #include
      7 #include
      8 #include
      9 #include
      10 #include
      11 #include
      12 #include
      13 #include
      14 #include
      15 #include
      16 #include
      17
      18 class MainWindow : public QMainWindow
      19 {
      20 Q_OBJECT
      21
      22 public:
      23 MainWindow(QWidget parent = nullptr);
      24 ~MainWindow();
      25
      26 private:
      27 /
      Udp 通信套接字 */
      28 QUdpSocket udpSocket;
      29
      30 /
      按钮 */
      31 QPushButton pushButton[4];
      32
      33 /
      标签文本 */
      34 QLabel label[3];
      35
      36 /
      水平容器 */
      37 QWidget hWidget[3];
      38
      39 /
      水平布局 */
      40 QHBoxLayout hBoxLayout[3];
      41
      42 /
      垂直容器 */
      43 QWidget vWidget;
      44
      45 /
      垂直布局 */
      46 QVBoxLayout vBoxLayout;
      47
      48 /
      文本浏览框 */
      49 QTextBrowser textBrowser;
      50
      51 /
      用于显示本地 ip */
      52 QComboBox comboBox[2];
      53
      54 /
      用于选择端口 */
      55 QSpinBox spinBox;
      56
      57 /
      文本输入框 */
      58 QLineEdit lineEdit;
      59
      60 /
      存储本地的 ip 列表地址 /
      61 QList IPlist;
      62
      63 /
      获取本地的所有 ip */
      64 void getLocalHostIP();

    • 主要是声明界面用的元素,及一些槽函数。重点是声明 udpSocket

  • mainwindow.cpp

    • 1 #include “mainwindow.h”
      2
      3 MainWindow::MainWindow(QWidget parent)
      4 : QMainWindow(parent)
      5{
      6 /
      设置主窗体的位置与大小 /
      7 this->setGeometry(0, 0, 800, 480);
      8
      9 /
      udp 套接字 /
      10 udpSocket = new QUdpSocket(this);
      11
      12 /
      参数 1 是设置 IP_MULTICAST_TTL 套接字选项允许应用程序主要限制数据包在
      Internet 中的生存时间,
      13 * 并防止其无限期地循环,数据报跨一个路由会减一,默认值为 1,表示多播仅适用于
      本地子网。/
      14 udpSocket->setSocketOption(QAbstractSocket::MulticastTtlOption,
      1);
      15
      16 /
      加入组播按钮 /
      17 pushButton[0] = new QPushButton();
      18 /
      退出组播按钮 /
      19 pushButton[1] = new QPushButton();
      20 /
      清空聊天文本按钮 /
      21 pushButton[2] = new QPushButton();
      22 /
      组播消息按钮 /
      23 pushButton[3] = new QPushButton();
      24
      25 /
      水平布局一 /
      26 hBoxLayout[0] = new QHBoxLayout();
      27 /
      水平布局二 /
      28 hBoxLayout[1] = new QHBoxLayout();
      29 /
      水平布局三 /
      30 hBoxLayout[2] = new QHBoxLayout();
      31 /
      水平布局四 /
      32 hBoxLayout[3] = new QHBoxLayout();
      33
      34 /
      水平容器一 /
      35 hWidget[0] = new QWidget();
      36 /
      水平容器二 /
      37 hWidget[1] = new QWidget();
      38 /
      水平容器三 /
      39 hWidget[2] = new QWidget();
      40
      41
      42 vWidget = new QWidget();
      43 vBoxLayout = new QVBoxLayout();
      44
      45 /
      标签实例化 /
      46 label[0] = new QLabel();
      47 label[1] = new QLabel();
      48 label[2] = new QLabel();
      49
      50 lineEdit = new QLineEdit();
      51 comboBox[0] = new QComboBox();
      52 comboBox[1] = new QComboBox();
      53 spinBox = new QSpinBox();
      54 textBrowser = new QTextBrowser();
      55
      56 label[0]->setText(“本地 IP 地址:”);
      57 label[1]->setText(“组播地址:”);
      58 label[2]->setText(“组播端口:”);
      59
      60 /
      设置标签根据文本文字大小自适应大小 /
      61 label[0]->setSizePolicy(QSizePolicy::Fixed,
      62 QSizePolicy::Fixed);
      63 label[1]->setSizePolicy(QSizePolicy::Fixed,
      64 QSizePolicy::Fixed);
      65 label[2]->setSizePolicy(QSizePolicy::Fixed,
      66 QSizePolicy::Fixed);
      67
      68 /
      设置端口号的范围,注意不要与主机的已使用的端口号冲突 /
      69 spinBox->setRange(10000, 99999);
      70
      71 pushButton[0]->setText(“加入组播”);
      72 pushButton[1]->setText(“退出组播”);
      73 pushButton[2]->setText(“清空文本”);
      74 pushButton[3]->setText(“组播消息”);
      75
      76 /
      设置停止监听状态不可用 /
      77 pushButton[1]->setEnabled(false);
      78
      79 /
      设置输入框默认的文本 /
      80 lineEdit->setText(“您好!”);
      81
      82 /
      默认添加范围内的一个组播地址 /
      83 comboBox[1]->addItem(“239.255.255.1”);
      84
      85 /
      设置可编辑,用户可自行修改此地址 /
      86 comboBox[1]->setEditable(true);
      87
      88 /
      水平布局一添加内容 /
      89 hBoxLayout[0]->addWidget(pushButton[0]);
      90 hBoxLayout[0]->addWidget(pushButton[1]);
      91 hBoxLayout[0]->addWidget(pushButton[2]);
      92
      93 /
      设置水平容器的布局为水平布局一 /
      94 hWidget[0]->setLayout(hBoxLayout[0]);
      95
      96 hBoxLayout[1]->addWidget(label[0]);
      97 hBoxLayout[1]->addWidget(comboBox[0]);
      98 hBoxLayout[1]->addWidget(label[1]);
      99 hBoxLayout[1]->addWidget(comboBox[1]);
      100 hBoxLayout[1]->addWidget(label[2]);
      101 hBoxLayout[1]->addWidget(spinBox);
      102
      103 /
      设置水平容器的布局为水平布局二 /
      104 hWidget[1]->setLayout(hBoxLayout[1]);
      105
      106 /
      水平布局三添加内容 /
      107 hBoxLayout[2]->addWidget(lineEdit);
      108 hBoxLayout[2]->addWidget(pushButton[3]);
      109
      110 /
      设置水平容器三的布局为水平布局一 /
      111 hWidget[2]->setLayout(hBoxLayout[2]);
      112
      113 /
      垂直布局添加内容 /
      114 vBoxLayout->addWidget(textBrowser);
      115 vBoxLayout->addWidget(hWidget[1]);
      116 vBoxLayout->addWidget(hWidget[0]);
      117 vBoxLayout->addWidget(hWidget[2]);
      118
      119 /
      设置垂直容器的布局为垂直布局 /
      120 vWidget->setLayout(vBoxLayout);
      121
      122 /
      居中显示 /
      123 setCentralWidget(vWidget);
      124
      125 /
      获取本地 ip /
      126 getLocalHostIP();
      127
      128 /
      信号槽连接 /
      129 connect(pushButton[0], SIGNAL(clicked()),
      130 this, SLOT(joinGroup()));
      131 connect(pushButton[1], SIGNAL(clicked()),
      132 this, SLOT(leaveGroup()));
      133 connect(pushButton[2], SIGNAL(clicked()),
      134 this, SLOT(clearTextBrowser()));
      135 connect(pushButton[3], SIGNAL(clicked()),
      136 this, SLOT(sendMessages()));
      137 connect(udpSocket, SIGNAL(readyRead()),
      138 this, SLOT(receiveMessages()));
      139 connect(udpSocket,
      140 SIGNAL(stateChanged(QAbstractSocket::SocketState)),
      141 this,
      142 SLOT(socketStateChange(QAbstractSocket::SocketState)));
      143 }
      144
      145 MainWindow::~MainWindow()
      146 {
      147 }
      148
      149 void MainWindow::joinGroup()
      150 {
      151 /
      获取端口 /
      152 quint16 port = spinBox->value();
      153 /
      获取组播地址 /
      154 QHostAddress groupAddr = QHostAddress(comboBox[1]->currentText());
      155
      156 /
      绑定端口需要在 socket 的状态为 UnconnectedState /
      157 if (udpSocket->state() != QAbstractSocket::UnconnectedState)
      158 udpSocket->close();
      159
      160 /
      加入组播前必须先绑定端口 /
      161 if (udpSocket->bind(QHostAddress::AnyIPv4,
      162 port, QUdpSocket::ShareAddress)) {
      163
      164 /
      加入组播组,返回结果给 ok 变量 /
      165 bool ok = udpSocket->joinMulticastGroup(groupAddr);
      166
      167 textBrowser->append(ok ? “加入组播成功” : “加入组播失败”);
      168
      169 textBrowser->append(“组播地址 IP:”
      170 + comboBox[1]->currentText());
      171
      172 textBrowser->append(“绑定端口:”
      173 + QString::number(port));
      174
      175 /
      设置界面中的元素的可用状态 /
      176 pushButton[0]->setEnabled(false);
      177 pushButton[1]->setEnabled(true);
      178 comboBox[1]->setEnabled(false);
      179 spinBox->setEnabled(false);
      180 }
      181 }
      182
      183 void MainWindow::leaveGroup()
      184 {
      185 /
      获取组播地址 /
      186 QHostAddress groupAddr = QHostAddress(comboBox[1]->currentText());
      187
      188 /
      退出组播 /
      189 udpSocket->leaveMulticastGroup(groupAddr);
      190
      191 /
      解绑,不再监听 /
      192 udpSocket->abort();
      193
      194 /
      设置界面中的元素的可用状态 /
      195 pushButton[0]->setEnabled(true);
      196 pushButton[1]->setEnabled(false);
      197 comboBox[1]->setEnabled(true);
      198 spinBox->setEnabled(true);
      199 }
      200
      201 /
      获取本地 IP /
      202 void MainWindow::getLocalHostIP()
      203 {
      204 // /
      获取主机的名称 /
      205 // QString hostName = QHostInfo::localHostName();
      206
      207 // /
      主机的信息 /
      208 // QHostInfo hostInfo = QHostInfo::fromName(hostName);
      209
      210 // /
      ip 列表,addresses 返回 ip 地址列表,注意主机应能从路由器获取到
      211 // * IP,否则可能返回空的列表(ubuntu 用此方法只能获取到环回 IP) /
      212 // IPlist = hostInfo.addresses();
      213 // qDebug()<<IPlist<<endl;
      214
      215 // /
      遍历 IPlist /
      216 // foreach (QHostAddress ip, IPlist) {
      217 // if (ip.protocol() == QAbstractSocket::IPv4Protocol)
      218 // comboBox->addItem(ip.toString());
      219 // }
      220
      221 /
      获取所有的网络接口,
      222 * QNetworkInterface 类提供主机的 IP 地址和网络接口的列表 /
      223 QList list
      224 = QNetworkInterface::allInterfaces();
      225
      226 /
      遍历 list /
      227 foreach (QNetworkInterface interface, list) {
      228
      229 /
      QNetworkAddressEntry 类存储 IP 地址子网掩码和广播地址 /
      230 QList entryList
      231 = interface.addressEntries();
      232
      233 /
      遍历 entryList /
      234 foreach (QNetworkAddressEntry entry, entryList) {
      235 /
      过滤 IPv6 地址,只留下 IPv4,并且不需要环回 IP /
      236 if (entry.ip().protocol() ==
      237 QAbstractSocket::IPv4Protocol &&
      238 ! entry.ip().isLoopback()) {
      239 /
      添加本地 IP 地址到 comboBox[0] /
      240 comboBox[0]->addItem(entry.ip().toString());
      241 /
      添加到 IP 列表中 /
      242 IPlist<<entry.ip();
      243 }
      244 }
      245 }
      246 }
      247
      248 /
      清除文本浏览框里的内容 /
      249 void MainWindow::clearTextBrowser()
      250 {
      251 /
      清除文本浏览器的内容 /
      252 textBrowser->clear();
      253 }
      254
      255 /
      客户端接收消息 /
      256 void MainWindow::receiveMessages()
      257 {
      258 /
      局部变量,用于获取发送者的 IP 和端口 /
      259 QHostAddress peerAddr;
      260 quint16 peerPort;
      261
      262 /
      如果有数据已经准备好 /
      263 while (udpSocket->hasPendingDatagrams()) {
      264 /
      udpSocket 发送的数据报是 QByteArray 类型的字节数组 /
      265 QByteArray datagram;
      266
      267 /
      重新定义数组的大小 /
      268 datagram.resize(udpSocket->pendingDatagramSize());
      269
      270 /
      读取数据,并获取发送方的 IP 地址和端口 /
      271 udpSocket->readDatagram(datagram.data(),
      272 datagram.size(),
      273 &peerAddr,
      274 &peerPort);
      275 /
      转为字符串 /
      276 QString str = datagram.data();
      277
      278 /
      显示信息到文本浏览框窗口 /
      279 textBrowser->append(“接收来自”
      280 + peerAddr.toString()
      281 + “:”
      282 + QString::number(peerPort)
      283 + str);
      284 }
      285 }
      286
      287 /
      客户端发送消息 /
      288 void MainWindow::sendMessages()
      289 {
      290 /
      文本浏览框显示发送的信息 /
      291 textBrowser->append(“发送:” + lineEdit->text());
      292
      293 /
      要发送的信息,转为 QByteArray 类型字节数组,数据一般少于 512 个字节 /
      294 QByteArray data = lineEdit->text().toUtf8();
      295
      296 /
      要发送的目标 Ip 地址 /
      297 QHostAddress groupAddr = QHostAddress(comboBox[1]->currentText());
      298
      299 /
      要发送的目标端口号 /
      300 quint16 groupPort = spinBox->value();
      301
      302 /
      发送消息 /
      303 udpSocket->writeDatagram(data, groupAddr, groupPort);
      304 }
      305
      306 /
      socket 状态改变 */
      307 void MainWindow::socketStateChange(QAbstractSocket::SocketState
      state)
      308 {
      309 switch (state) {
      310 case QAbstractSocket::UnconnectedState:
      311 textBrowser->append(“scoket 状态:UnconnectedState”);
      312 break;
      313 case QAbstractSocket::ConnectedState:
      314 textBrowser->append(“scoket 状态:ConnectedState”);
      315 break;
      316 case QAbstractSocket::ConnectingState:
      317 textBrowser->append(“scoket 状态:ConnectingState”);
      318 break;
      319 case QAbstractSocket::HostLookupState:
      320 textBrowser->append(“scoket 状态:HostLookupState”);
      321 break;
      322 case QAbstractSocket::ClosingState:
      323 textBrowser->append(“scoket 状态:ClosingState”);
      324 break;
      325 case QAbstractSocket::ListeningState:
      326 textBrowser->append(“scoket 状态:ListeningState”);
      327 break;
      328 case QAbstractSocket::BoundState:
      329 textBrowser->append(“scoket 状态:BoundState”);
      330 break;
      331 default:
      332 break;
      333 }
      334 }

    • 绑定端口(161-162行)

      • 使用 bind 方法绑定一个端口。需要注意的是,绑定的端口不能与主机上已经使用的端口冲突
    • 加入组播(165行)

      • 使用 joinMulticastGroup 方法加入组播

      • QHostAddress::AnyIPv4 是用于加入 IPv4 组播的接口

      • 注意不是所有操作系统都支持不带接口选择的加入 IPv6 组播组

      • 加入组播的结果返回给变量 ok

      • 组播地址可以通过用户点击 comboBox[1] 控件输入,默认地址为 239.255.255.1

      • 组播地址的范围必须在 239.0.0.0~239.255.255.255 之间

    • 退出组播(189行)

      • 使用 leaveMulticastGroup 方法退出组播
    • 解绑端口(192行)

      • 使用 abort 方法解除绑定的端口
    • 接收消息(256-285行)

      • 接收消息,消息类型为 QByteArray 字节数组

      • 使用 readDatagram 方法读取数据包

      • 在 readDatagram 方法中,可以获取对方的套接字 IP 地址和端口号

    • 发送消息(288-304行)

      • 发送消息,与广播消息或单播消息不同的是,将目标 IP 地址换成了组播地址 239.255.255.1

程序运行效果

  • 运行程序

    • 运行程序后,用户需要点击“加入组播”按钮,然后点击“组播消息”按钮

    • 可以作为消息的发送者,也可以作为消息的接收者

  • 在同一台主机上运行多个实例

    • 如果在同一台主机上运行两个实例程序,不能绑定同一个端口,否则会产生冲突
  • 在同一局域网内不同主机上运行

    • 如果在同一局域网内的不同主机上运行此程序,绑定的端口号可以相同
  • 组播消息的接收

    • 由于是组播消息,因此发送消息的主机也会接收到自己发送的消息
  • 局域网内其他主机的通信

    • 如果在局域网内的其他主机上运行此程序,当点击“加入组播”按钮后,这些主机也可以收发组播消息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木木不迷茫(˵¯͒¯͒˵)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值