**摘要:**本文详细介绍了UDP编程的基本流程、函数接口,并深入讲解了广播和组播两种网络通信方式。通过对比TCP与UDP的区别,结合完整的代码示例和流程图,帮助读者全面理解UDP协议的特点和应用场景。
1. UDP编程基础
1.1 UDP编程流程

UDP通信流程(类比发短信):
服务器端流程:
- 创建数据报套接字 (
socket(AF_INET, SOCK_DGRAM, 0))→ 相当于拥有手机- 绑定网络信息 (
bind())→ 相当于绑定手机号码- 接收信息 (
recvfrom())→ 接收短信- 关闭套接字 (
close())→ 通信结束客户端流程:
- 创建数据报套接字 (
socket(AF_INET, SOCK_DGRAM, 0))→ 相当于拥有手机- 指定服务器的网络信息 → 知道对方的手机号码
- 发送信息 (
sendto())→ 发送短信- 关闭套接字 (
close())→ 发送完毕
1.2 UDP核心函数接口
recvfrom() 函数
cpp
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收UDP数据包
参数:
sockfd:套接字描述符
buf:接收缓冲区的首地址
len:接收缓冲区的大小
flags:通常设置为0
src_addr:发送端的网络信息结构体指针(可获取发送方地址)
addrlen:发送端网络信息结构体大小的指针
返回值:
成功:接收到的字节数
失败:-1,并设置errno
sendto() 函数
cpp
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
功能:发送UDP数据包
参数:
sockfd:套接字描述符
buf:发送缓冲区的首地址
len:发送缓冲区的大小
flags:通常设置为0
dest_addr:接收端的网络信息结构体指针
addrlen:接收端网络信息结构体的大小
返回值:
成功:发送的字节数
失败:-1,并设置errno
重要说明: 在UDP中,recvfrom()函数无法检测客户端是否异常退出,因为UDP是无连接的。
1.3 UDP服务器端完整示例
cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
// 1. 创建数据报套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket err");
return -1;
}
// 2. 填充服务器地址结构体
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr(argv[1]); // IP地址
saddr.sin_port = htons(atoi(argv[2])); // 端口号
// 3. 绑定网络信息
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) {
perror("bind err");
return -1;
}
printf("bind ok\n");
// 4. 接收数据
char buf[128] = "";
recvfrom(sockfd, buf, 128, 0, NULL, NULL);
printf("Received: %s\n", buf);
// 5. 关闭套接字
close(sockfd);
return 0;
}
1.4 UDP客户端完整示例
cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
// 1. 创建数据报套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket err");
return -1;
}
// 2. 填充服务器地址结构体
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr(argv[1]); // 服务器IP
saddr.sin_port = htons(atoi(argv[2])); // 服务器端口
// 3. 发送数据
char buf[128] = "hello";
sendto(sockfd, buf, 128, 0, (struct sockaddr *)&saddr, sizeof(saddr));
printf("send ok\n");
// 4. 关闭套接字
close(sockfd);
return 0;
}
1.5 双向通信示例
**练习:**实现客户端发送"hello"给服务器端,服务器接收后回复"recv:hello!!!!!"。
UDP服务器端(双向通信)
cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
// 1. 创建数据报套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket err");
return -1;
}
// 2. 填充服务器地址结构体
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr(argv[1]);
saddr.sin_port = htons(atoi(argv[2]));
// 3. 绑定网络信息
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) {
perror("bind err");
return -1;
}
printf("bind ok\n");
// 4. 接收客户端数据
char buf[128] = "";
int len = sizeof(caddr);
recvfrom(sockfd, buf, 128, 0, (struct sockaddr *)&caddr, &len);
printf("Client says: %s\n", buf);
// 5. 发送回复给客户端
memset(buf, 0, 128);
strcpy(buf, "recv:hello!!!!!");
sendto(sockfd, buf, 128, 0, (struct sockaddr *)&caddr, sizeof(caddr));
// 6. 关闭套接字
close(sockfd);
return 0;
}
UDP客户端(双向通信)
cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
// 1. 创建数据报套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket err");
return -1;
}
// 2. 填充服务器地址结构体
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr(argv[1]);
saddr.sin_port = htons(atoi(argv[2]));
// 3. 发送数据到服务器
char buf[128] = "hello";
sendto(sockfd, buf, 128, 0, (struct sockaddr *)&saddr, sizeof(saddr));
printf("send ok\n");
// 4. 接收服务器回复
recvfrom(sockfd, buf, 128, 0, NULL, NULL);
printf("Server reply: %s\n", buf);
// 5. 关闭套接字
close(sockfd);
return 0;
}
1.6 UDP与TCP的重要区别
- 连接方式:TCP必须先运行服务器,客户端才能连接;UDP服务器和客户端运行顺序没有先后,因为UDP是无连接的。
- 并发处理 :一个UDP服务器可以同时处理多个客户端请求,可以通过
recvfrom()的src_addr参数获取客户端IP和端口号。 - connect()函数的作用 :在UDP中,客户端使用
send()前可以调用connect(),但这并不是建立连接,而是指定默认的发送目标,之后就可以使用send()代替sendto()。 - 函数通用性 :在TCP中也可以使用
recvfrom()和sendto(),使用时将最后两个参数设置为NULL即可。
2. 广播通信
2.1 广播理论基础
前面介绍的数据包发送方式只有一个接收方,称为单播。
- 广播定义 :如果同时发给局域网中的所有主机,称为广播。
- 协议限制:只有用户数据报(使用UDP协议)套接字才能广播。
- 应用场景:一般被设计成局域网搜索协议,如DHCP、ARP等。
- 广播地址 :
- 以192.168.1.0(子网掩码255.255.255.0)网段为例
- 最大的主机地址192.168.1.255代表该网段的广播地址
- 发到该地址的数据包被网段内所有主机接收
2.2 setsockopt()函数详解
Linux中socket属性设置函数:

cpp
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, void *optval, socklen_t optlen);
功能:设置套接字属性
参数:
sockfd:套接字描述符(指定要设置/获取哪个套接字的属性)
level:协议层(指定要控制的协议层次)
SOL_SOCKET(应用层) - 通用套接字选项
IPPROTO_TCP(传输层)
IPPROTO_IP(网络层)
optname:选项名(指定要控制的内容和方式)
--- SOL_SOCKET常用选项(man 7 socket)---
SO_REUSEADDR:允许端口快速重用(int*)
SO_BROADCAST:允许发送广播数据(int)
SO_RCVBUF:接收缓冲区大小(int)
SO_SNDBUF:发送缓冲区大小(int)
SO_RCVTIMEO:接收超时(struct timeval)
SO_SNDTIMEO:发送超时(struct timeval)
optval:根据optname不同,该指针类型不同
optlen:optval指针指向的内存空间的大小
返回值:
成功:返回0
失败:返回-1,并更新errno
man 7 socket文档截图:

2.3 广播发送和接收流程
发送广播流程
cpp
int opt = 1; // 类似于广播开关,0为关闭,1为打开
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));
- 创建数据报套接字
- 填充结构体信息(使用广播IP地址)
- 允许发送广播数据
- 发送数据
- 关闭套接字
接收广播流程
- 创建数据报套接字
- 填充结构体信息(使用广播IP地址)
- 绑定到广播地址
- 接收数据
- 关闭套接字
2.4 广播通信完整示例
广播发送端(send.c)
cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
// 1. 创建数据报套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket err");
return -1;
}
// 2. 填充广播地址结构体
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr(argv[1]); // 广播IP地址
saddr.sin_port = htons(atoi(argv[2])); // 端口号
// 3. 允许发送广播数据
int opt = 1; // 类似于广播开关,0为关闭,1为打开
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));
// 4. 发送广播数据
char buf[128] = "hello";
sendto(sockfd, buf, 128, 0, (struct sockaddr *)&saddr, sizeof(saddr));
printf("Broadcast send ok\n");
// 5. 关闭套接字
close(sockfd);
return 0;
}
广播接收端(recv.c)