UDP 单播、多播、广播:原理、实践
一、引言
在计算机网络通信领域,UDP(User Datagram Protocol,用户数据报协议)是一种重要的传输层协议。它以无连接、低开销的特点,在众多实时性要求高的应用场景中发挥关键作用。UDP 支持单播、多播和广播三种通信模式,每种模式都有其独特的应用场景和工作原理。深入理解这些通信模式,对于开发高效的网络应用程序至关重要。
二、UDP 基础概述
UDP 是一种无连接的传输层协议,它在网络层 IP 协议的基础上,为应用层提供了简单的数据传输服务。与面向连接的 TCP(Transmission Control Protocol)协议不同,UDP 不保证数据的可靠传输、顺序交付以及数据的完整性。然而,UDP 的这些特性使得它在一些对实时性要求较高,对数据准确性要求相对较低的场景中具有显著优势,如实时音频和视频流、在线游戏、网络管理等。
UDP 数据报由首部和数据两部分组成。首部长度固定为 8 字节,包含四个字段:
- 源端口号(16 位):标识发送端应用程序的端口,用于接收端回发数据。
- 目的端口号(16 位):标识接收端应用程序的端口,用于确定数据的接收方。
- 长度(16 位):UDP 数据报的总长度,包括首部和数据部分,最小值为 8 字节(仅首部)。
- 校验和(16 位):用于检测数据报在传输过程中是否发生错误,但校验和是可选的,若不使用则该字段设为全零。
三、UDP 单播
3.1 概念描述
UDP 单播是一种一对一的通信方式,发送端向特定的一个接收端发送数据。在这种通信模式下,发送端在 UDP 数据报的首部中明确指定接收端的 IP 地址和端口号。网络设备根据数据报中的目的 IP 地址,通过路由算法将数据报转发到目标接收端。单播通信的特点是数据传输的针对性强,只有目标接收端能够接收到数据,适用于大多数需要精确通信的场景,如客户端 - 服务器模型的应用程序。
3.2 示例代码实现
3.2.1 发送端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8080
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 1024
int main(int argc, char const *argv[]) {
int sockfd;
struct sockaddr_in servaddr;
char buffer[BUFFER_SIZE];
// 创建 UDP 套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
memset(&servaddr, 0, sizeof(servaddr));
memset(buffer, 0, sizeof(buffer));
// 设置服务器地址
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);
char *msg = "Hello, Server!";
sendto(sockfd, (const char *)msg, strlen(msg), MSG_CONFIRM, (const struct sockaddr *)&servaddr, sizeof(servaddr));
printf("Message sent to server: %s\n", msg);
close(sockfd);
return 0;
}
3.2.2 接收端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main(int argc, char const *argv[]) {
int sockfd;
struct sockaddr_in servaddr, cliaddr;
char buffer[BUFFER_SIZE];
// 创建 UDP 套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
memset(&servaddr, 0, sizeof(servaddr));
memset(&cliaddr, 0, sizeof(cliaddr));
// 设置服务器地址
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(PORT);
// 绑定套接字到地址
if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("bind failed");
close(sockfd);
exit(EXIT_FAILURE);
}
socklen_t len = sizeof(cliaddr);
int n = recvfrom(sockfd, (char *)buffer, BUFFER_SIZE, MSG_WAITALL, (struct sockaddr *)&cliaddr, &len);
buffer[n] = '\0';
printf("Message received from client: %s\n", buffer);
close(sockfd);
return 0;
}
3.3 编译和测试过程
-
编译
- 在终端中,切换到包含
udp_unicast_sender.c
和udp_unicast_receiver.c
文件的目录。 - 编译发送端代码:
gcc -o udp_unicast_sender udp_unicast_sender.c
- 编译接收端代码:
gcc -o udp_unicast_receiver udp_unicast_receiver.c
- 在终端中,切换到包含
-
测试
- 首先在一个终端中运行接收端程序:
./udp_unicast_receiver
- 然后在另一个终端中运行发送端程序:
./udp_unicast_sender
- 接收端将显示接收到的消息:
Message received from client: Hello, Server!
- 首先在一个终端中运行接收端程序:
四、UDP 广播
4.1 概念描述
UDP 广播是一种一对所有的通信方式,发送端向网络中的所有设备发送数据。广播地址分为两种类型:有限广播和直接广播。
- 有限广播:使用地址 255.255.255.255,它是一种受限的广播地址,仅在本地网络内进行广播,路由器不会转发以有限广播地址为目的地址的数据包。
- 直接广播:网络地址的主机位全为 1 的地址。例如,对于网络 192.168.1.0/24,其直接广播地址为 192.168.1.255。直接广播数据包可以被路由器转发到指定网络的所有主机。
4.2 原理
4.2.1 有限广播原理
-
发送端
- 发送端创建 UDP 套接字,并设置套接字选项以允许广播(
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable))
),其中broadcastEnable
为一个非零值,通常设为 1。 - 构造 UDP 数据报,将目的 IP 地址设置为 255.255.255.255,目的端口号设置为接收端监听的端口号。
- 使用
sendto
函数发送数据报,数据报将在本地网络内广播,所有监听该端口的设备都能接收。
- 发送端创建 UDP 套接字,并设置套接字选项以允许广播(
-
接收端
- 接收端创建 UDP 套接字,并绑定到指定的端口号。
- 使用
recvfrom
函数接收数据报,当有广播数据到达时,接收端就能接收到。
4.2.2 直接广播原理
-
发送端
- 同样,发送端先创建 UDP 套接字并设置允许广播选项。
- 构造 UDP 数据报时,将目的 IP 地址设置为目标网络的直接广播地址,目的端口号设置为接收端监听的端口号。
- 使用
sendto
函数发送数据报,路由器会将该数据报转发到目标网络的所有主机。
-
接收端
- 接收端在目标网络内,创建 UDP 套接字并绑定到指定端口号。
- 使用
recvfrom
函数接收数据报,接收来自发送端的广播数据。
4.3 流程示意图
4.3.1 有限广播流程示意图
4.3.2 直接广播流程示意图
4.4 示例代码实现
4.4.1 有限广播发送端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8080
#define LIMITED_BROADCAST_IP "255.255.255.255"
#define BUFFER_SIZE 1024
int main(int argc, char const *argv[]) {
int sockfd;
struct sockaddr_in servaddr;
char buffer[BUFFER_SIZE];
// 创建 UDP 套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
int broadcastEnable = 1;
// 设置套接字选项以允许广播
if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable)) < 0) {
perror("setsockopt failed");
close(sockfd);
exit(EXIT_FAILURE);
}
memset(&servaddr, 0, sizeof(servaddr));
memset(buffer, 0, sizeof(buffer));
// 设置有限广播地址
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
servaddr.sin_addr.s_addr = inet_addr(LIMITED_BROADCAST_IP);
char *msg = "Hello, Local Network!";
sendto(sockfd, (const char *)msg, strlen(msg), MSG_CONFIRM, (const struct sockaddr *)&servaddr, sizeof(servaddr));
printf("Limited broadcast message sent: %s\n", msg);
close(sockfd);
return 0;
}
4.4.2 有限广播接收端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main(int argc, char const *argv[]) {
int sockfd;
struct sockaddr_in servaddr, cliaddr;
char buffer[BUFFER_SIZE];
// 创建 UDP 套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
memset(&servaddr, 0, sizeof(servaddr));
memset(&cliaddr, 0, sizeof(cliaddr));
// 设置服务器地址
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(PORT);
// 绑定套接字到地址
if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("bind failed");
close(sockfd);
exit(EXIT_FAILURE);
}
socklen_t len = sizeof(cliaddr);
int n = recvfrom(sockfd, (char *)buffer, BUFFER_SIZE, MSG_WAITALL, (struct sockaddr *)&cliaddr, &len);
buffer[n] = '\0';
printf("Limited broadcast message received: %s\n", buffer);
close(sockfd);
return 0;
}
4.4.3 直接广播发送端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8080
#define DIRECT_BROADCAST_IP "192.168.1.255"
#define BUFFER_SIZE 1024
int main(int argc, char const *argv[]) {
int sockfd;
struct sockaddr_in servaddr;
char buffer[BUFFER_SIZE];
// 创建 UDP 套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
int broadcastEnable = 1;
// 设置套接字选项以允许广播
if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable)) < 0) {
perror("setsockopt failed");
close(sockfd);
exit(EXIT_FAILURE);
}
memset(&servaddr, 0, sizeof(servaddr));
memset(buffer, 0, sizeof(buffer));
// 设置直接广播地址
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
servaddr.sin_addr.s_addr = inet_addr(DIRECT_BROADCAST_IP);
char *msg = "Hello, 192.168.1.0 Network!";
sendto(sockfd, (const char *)msg, strlen(msg), MSG_CONFIRM, (const struct sockaddr *)&servaddr, sizeof(servaddr));
printf("Direct broadcast message sent: %s\n", msg);
close(sockfd);
return 0;
}
4.4.4 直接广播接收端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main(int argc, char const *argv[]) {
int sockfd;
struct sockaddr_in servaddr, cliaddr;
char buffer[BUFFER_SIZE];
// 创建 UDP 套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
memset(&servaddr, 0, sizeof(servaddr));
memset(&cliaddr, 0, sizeof(cliaddr));
// 设置服务器地址
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(PORT);
// 绑定套接字到地址
if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("bind failed");
close(sockfd);
exit(EXIT_FAILURE);
}
socklen_t len = sizeof(cliaddr);
int n = recvfrom(sockfd, (char *)buffer, BUFFER_SIZE, MSG_WAITALL, (struct sockaddr *)&cliaddr, &len);
buffer[n] = '\0';
printf("Direct broadcast message received: %s\n", buffer);
close(sockfd);
return 0;
}
4.5 编译和测试过程
- 编译
- 有限广播
- 在终端中,切换到包含
udp_limited_broadcast_sender.c
和udp_limited_broadcast_receiver.c
文件的目录。 - 编译发送端代码:
gcc -o udp_limited_broadcast_sender udp_limited_broadcast_sender.c
- 编译接收端代码:
gcc -o udp_limited_broadcast_receiver udp_limited_broadcast_receiver.c
- 在终端中,切换到包含
- 直接广播
- 切换到包含
udp_direct_broadcast_sender.c
和udp_direct_broadcast_receiver.c
文件的目录。 - 编译发送端代码:
gcc -o udp_direct_broadcast_sender udp_direct_broadcast_sender.c
- 编译接收端代码:
gcc -o udp_direct_broadcast_receiver udp_direct_broadcast_receiver.c
- 切换到包含
- 有限广播
- 测试
- 有限广播
- 首先在本地网络内的多个终端中运行接收端程序:
./udp_limited_broadcast_receiver
- 然后在另一个终端中运行发送端程序:
./udp_limited_broadcast_sender
- 每个接收端都将显示接收到的有限广播消息:
Limited broadcast message received: Hello, Local Network!
- 首先在本地网络内的多个终端中运行接收端程序:
- 直接广播
- 在目标网络(192.168.1.0/24 为例)内的多个终端中运行接收端程序:
./udp_direct_broadcast_receiver
- 在可以向该目标网络发送直接广播的设备上运行发送端程序:
./udp_direct_broadcast_sender
- 目标网络内的每个接收端都将显示接收到的直接广播消息:
Direct broadcast message received: Hello, 192.168.1.0 Network!
- 在目标网络(192.168.1.0/24 为例)内的多个终端中运行接收端程序:
- 有限广播
五、UDP 多播
5.1 概念描述
UDP 多播(也称为组播)是一种一对多的通信方式,发送端向一组特定的接收端发送数据。这组接收端通过加入同一个多播组来接收数据。多播使用 D 类 IP 地址(范围是 224.0.0.0 到 239.255.255.255)来标识多播组。发送端将数据发送到多播组的 IP 地址,网络会将数据转发给组内的所有成员。多播适用于一些需要向特定的一组设备发送相同数据的场景,如在线视频会议、流媒体分发等。与广播不同,多播不会向网络中的所有设备发送数据,只有加入了相应多播组的设备才会接收数据,这样可以减少网络流量,提高传输效率。
5.2 原理
-
发送端
- 发送端创建 UDP 套接字。
- 构造 UDP 数据报时,将目的 IP 地址设置为多播组的 IP 地址,目的端口号设置为接收端监听的端口号。
- 使用
sendto
函数将数据报发送出去,网络会根据多播路由协议将数据转发到多播组的成员。发送端不需要关心组内成员的具体 IP 地址,只需要知道多播组的地址。
-
接收端
- 接收端创建 UDP 套接字,并绑定到指定的端口号。
- 为了加入多播组,接收端需要使用
setsockopt
函数设置套接字选项。具体来说,需要设置IP_ADD_MEMBERSHIP
选项,代码如下:
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_IP);
mreq.imr_interface.s_addr = INADDR_ANY;
if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
perror("setsockopt failed");
close(sockfd);
exit(EXIT_FAILURE);
}
- 这里,
mreq
结构体包含多播组的 IP 地址和本地接口地址。通过设置IP_ADD_MEMBERSHIP
选项,接收端通知操作系统将该套接字加入指定的多播组。之后,接收端使用recvfrom
函数接收数据报,就能接收到发往该多播组的所有数据。
5.3 流程示意图
5.4 示例代码实现
5.4.1 多播发送端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8080
#define MULTICAST_IP "224.1.1.1"
#define BUFFER_SIZE 1024
int main(int argc, char const *argv[]) {
int sockfd;
struct sockaddr_in servaddr;
char buffer[BUFFER_SIZE];
// 创建 UDP 套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
memset(&servaddr, 0, sizeof(servaddr));
memset(buffer, 0, sizeof(buffer));
// 设置多播地址
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
servaddr.sin_addr.s_addr = inet_addr(MULTICAST_IP);
char *msg = "Hello, Multicast Group!";
sendto(sockfd, (const char *)msg, strlen(msg), MSG_CONFIRM, (const struct sockaddr *)&servaddr, sizeof(servaddr));
printf("Multicast message sent: %s\n", msg);
close(sockfd);
return 0;
}
5.4.2 多播接收端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8080
#define MULTICAST_IP "224.1.1.1"
#define BUFFER_SIZE 1024
int main(int argc, char const *argv[]) {
int sockfd;
struct sockaddr_in servaddr, cliaddr;
struct ip_mreq mreq;
char buffer[BUFFER_SIZE];
// 创建 UDP 套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
memset(&servaddr, 0, sizeof(servaddr));
memset(&cliaddr, 0, sizeof(cliaddr));
// 设置服务器地址
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(PORT);
// 绑定套接字到地址
if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("bind failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// 设置多播组地址和本地接口地址
mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_IP);
mreq.imr_interface.s_addr = INADDR_ANY;
// 加入多播组
if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
perror("setsockopt failed");
close(sockfd);
exit(EXIT_FAILURE);
}
socklen_t len = sizeof(cliaddr);
int n = recvfrom(sockfd, (char *)buffer, BUFFER_SIZE, MSG_WAITALL, (struct sockaddr *)&cliaddr, &len);
buffer[n] = '\0';
printf("Multicast message received: %s\n", buffer);
close(sockfd);
return 0;
}
5.5 编译和测试过程
-
编译
- 在终端中,切换到包含
udp_multicast_sender.c
和udp_multicast_receiver.c
文件的目录。 - 编译发送端代码:
gcc -o udp_multicast_sender udp_multicast_sender.c
- 编译接收端代码:
gcc -o udp_multicast_receiver udp_multicast_receiver.c
- 在终端中,切换到包含
-
测试
- 首先在多个终端中运行接收端程序(模拟多播组成员):
./udp_multicast_receiver
- 然后在另一个终端中运行发送端程序:
./udp_multicast_sender
- 每个接收端都将显示接收到的多播消息:`Multicast message received: Hello, Multicast Group!
- 首先在多个终端中运行接收端程序(模拟多播组成员):
原文地址:https://blog.csdn.net/pigliuxu/article/details/145230866
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!