一、引言
在计算机网络通信领域,UDP(User Datagram Protocol,用户数据报协议)是一种重要的传输层协议。它以无连接、低开销的特点,在众多实时性要求高的应用场景中发挥关键作用。UDP 支持单播、多播和广播三种通信模式,每种模式都有其独特的应用场景和工作原理。深入理解这些通信模式,对于开发高效的网络应用程序至关重要。
UDP__3">二、UDP 基础概述
UDP 是一种无连接的传输层协议,它在网络层 IP 协议的基础上,为应用层提供了简单的数据传输服务。与面向连接的 TCP(Transmission Control Protocol)协议不同,UDP 不保证数据的可靠传输、顺序交付以及数据的完整性。然而,UDP 的这些特性使得它在一些对实时性要求较高,对数据准确性要求相对较低的场景中具有显著优势,如实时音频和视频流、在线游戏、网络管理等。
UDP 数据报由首部和数据两部分组成。首部长度固定为 8 字节,包含四个字段:
- 源端口号(16 位):标识发送端应用程序的端口,用于接收端回发数据。
- 目的端口号(16 位):标识接收端应用程序的端口,用于确定数据的接收方。
- 长度(16 位):UDP 数据报的总长度,包括首部和数据部分,最小值为 8 字节(仅首部)。
- 校验和(16 位):用于检测数据报在传输过程中是否发生错误,但校验和是可选的,若不使用则该字段设为全零。
UDP__14">三、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 1024int 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 1024int 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__124">四、UDP 广播
4.1 概念描述
UDP 广播是一种一对所有的通信方式,发送端向网络中的所有设备发送数据。广播地址分为两种类型:有限广播和直接广播。
- 有限广播:使用地址 255.255.255.255,它是一种受限的广播地址,仅在本地网络内进行广播,路由器不会转发以有限广播地址为目的地址的数据包。
- 直接广播:网络地址的主机位全为 1 的地址。例如,对于网络 192.168.1.0/24,其直接广播地址为 192.168.1.255。直接广播数据包可以被路由器转发到指定网络的所有主机。
4.2 原理
4.2.1 有限广播原理
-
发送端
-
接收端
- 接收端创建 UDP 套接字,并绑定到指定的端口号。
- 使用
recvfrom
函数接收数据报,当有广播数据到达时,接收端就能接收到。
4.2.2 直接广播原理
-
发送端
-
接收端
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 1024int 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 1024int 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 1024int 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 1024int 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.html" title=udp>udp_limited_broadcast_sender.c
和UDP.html" title=udp>udp_limited_broadcast_receiver.c
文件的目录。 - 编译发送端代码:
gcc -o UDP.html" title=udp>udp_limited_broadcast_sender UDP.html" title=udp>udp_limited_broadcast_sender.c
- 编译接收端代码:
gcc -o UDP.html" title=udp>udp_limited_broadcast_receiver UDP.html" title=udp>udp_limited_broadcast_receiver.c
- 在终端中,切换到包含
- 直接广播
- 切换到包含
UDP.html" title=udp>udp_direct_broadcast_sender.c
和UDP.html" title=udp>udp_direct_broadcast_receiver.c
文件的目录。 - 编译发送端代码:
gcc -o UDP.html" title=udp>udp_direct_broadcast_sender UDP.html" title=udp>udp_direct_broadcast_sender.c
- 编译接收端代码:
gcc -o UDP.html" title=udp>udp_direct_broadcast_receiver UDP.html" title=udp>udp_direct_broadcast_receiver.c
- 切换到包含
- 有限广播
- 测试
- 有限广播
- 直接广播
UDP__395">五、UDP 多播
5.1 概念描述
UDP 多播(也称为组播)是一种一对多的通信方式,发送端向一组特定的接收端发送数据。这组接收端通过加入同一个多播组来接收数据。多播使用 D 类 IP 地址(范围是 224.0.0.0 到 239.255.255.255)来标识多播组。发送端将数据发送到多播组的 IP 地址,网络会将数据转发给组内的所有成员。多播适用于一些需要向特定的一组设备发送相同数据的场景,如在线视频会议、流媒体分发等。与广播不同,多播不会向网络中的所有设备发送数据,只有加入了相应多播组的设备才会接收数据,这样可以减少网络流量,提高传输效率。
5.2 原理
-
发送端
-
接收端
- 接收端创建 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 1024int 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 1024int 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.html" title=udp>udp_multicast_sender.c
和UDP.html" title=udp>udp_multicast_receiver.c
文件的目录。 - 编译发送端代码:
gcc -o UDP.html" title=udp>udp_multicast_sender UDP.html" title=udp>udp_multicast_sender.c
- 编译接收端代码:
gcc -o UDP.html" title=udp>udp_multicast_receiver UDP.html" title=udp>udp_multicast_receiver.c
- 在终端中,切换到包含
-
测试