在计算机网络通信领域,UDP(User Datagram Protocol,用户数据报协议)是一种重要的传输层协议。它以无连接、低开销的特点,在众多实时性要求高的应用场景中发挥关键作用。UDP 支持单播、多播和广播三种通信模式,每种模式都有其独特的应用场景和工作原理。深入理解这些通信模式,对于开发高效的网络应用程序至关重要。
UDP 是一种无连接的传输层协议,它在网络层 IP 协议的基础上,为应用层提供了简单的数据传输服务。与面向连接的 TCP(Transmission Control Protocol)协议不同,UDP 不保证数据的可靠传输、顺序交付以及数据的完整性。然而,UDP 的这些特性使得它在一些对实时性要求较高,对数据准确性要求相对较低的场景中具有显著优势,如实时音频和视频流、在线游戏、网络管理等。
UDP 数据报由首部和数据两部分组成。首部长度固定为 8 字节,包含四个字段:
UDP 单播是一种一对一的通信方式,发送端向特定的一个接收端发送数据。在这种通信模式下,发送端在 UDP 数据报的首部中明确指定接收端的 IP 地址和端口号。网络设备根据数据报中的目的 IP 地址,通过路由算法将数据报转发到目标接收端。单播通信的特点是数据传输的针对性强,只有目标接收端能够接收到数据,适用于大多数需要精确通信的场景,如客户端 - 服务器模型的应用程序。
#include
#include
#include
#include
#include
#include
#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;
}
#include
#include
#include
#include
#include
#include
#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;
}
编译
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 广播是一种一对所有的通信方式,发送端向网络中的所有设备发送数据。广播地址分为两种类型:有限广播和直接广播。
发送端
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable))
),其中 broadcastEnable
为一个非零值,通常设为 1。sendto
函数发送数据报,数据报将在本地网络内广播,所有监听该端口的设备都能接收。接收端
recvfrom
函数接收数据报,当有广播数据到达时,接收端就能接收到。发送端
sendto
函数发送数据报,路由器会将该数据报转发到目标网络的所有主机。接收端
recvfrom
函数接收数据报,接收来自发送端的广播数据。#include
#include
#include
#include
#include
#include
#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;
}
#include
#include
#include
#include
#include
#include
#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;
}
#include
#include
#include
#include
#include
#include
#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;
}
#include
#include
#include
#include
#include
#include
#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;
}
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!
./udp_direct_broadcast_receiver
./udp_direct_broadcast_sender
Direct broadcast message received: Hello, 192.168.1.0 Network!
UDP 多播(也称为组播)是一种一对多的通信方式,发送端向一组特定的接收端发送数据。这组接收端通过加入同一个多播组来接收数据。多播使用 D 类 IP 地址(范围是 224.0.0.0 到 239.255.255.255)来标识多播组。发送端将数据发送到多播组的 IP 地址,网络会将数据转发给组内的所有成员。多播适用于一些需要向特定的一组设备发送相同数据的场景,如在线视频会议、流媒体分发等。与广播不同,多播不会向网络中的所有设备发送数据,只有加入了相应多播组的设备才会接收数据,这样可以减少网络流量,提高传输效率。
发送端
sendto
函数将数据报发送出去,网络会根据多播路由协议将数据转发到多播组的成员。发送端不需要关心组内成员的具体 IP 地址,只需要知道多播组的地址。接收端
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
函数接收数据报,就能接收到发往该多播组的所有数据。#include
#include
#include
#include
#include
#include
#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;
}
#include
#include
#include
#include
#include
#include
#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;
}
编译
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