UDP(User Datagram Protocol,用户数据报协议)
UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议,与 TCP(传输控制协议)并列为互联网的两大主流传输协议。它具有简单、高效的特点,适用于对实时性要求高但对可靠性要求相对较低的场景。
UDP 的核心特点
- 无连接:发送数据前不需要建立连接(如 TCP 的三次握手),直接发送数据报。
- 不可靠:不保证数据的可靠传输,可能出现丢包、乱序或重复。
- 面向数据报:UDP 将应用层数据封装成完整的数据报,直接传输。
- 开销小:UDP 头部仅 8 字节(相比 TCP 的 20 字节),传输效率高。
- 无拥塞控制:UDP 不会根据网络状况调整发送速率,可能导致网络拥塞。
UDP 头部格式
UDP 数据报由 8 字节头部和数据部分组成:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 源端口号 | 目标端口号 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 长度 | 校验和 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 数据 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
源端口号:发送方端口(可选,0 表示无端口)。
目标端口号:接收方端口。
长度:UDP 数据报总长度(头部 + 数据,最小 8 字节)。
校验和:用于检测数据传输错误(可选,全 0 表示不使用)。
UDP 的优缺点
优点
低延迟:无需连接建立和维护,适合实时应用(如视频流、语音通话)。
简单高效:协议开销小,处理速度快。
广播 / 多播支持:可向多个目标发送数据。
缺点
不可靠:不保证数据到达,需要应用层自行处理错误。
无流量控制:可能导致网络拥塞或接收方缓冲区溢出。
UDP 的典型应用场景
- 实时音视频传输(如 Zoom、YouTube 直播)
- 游戏网络通信(如 FPS 游戏,对延迟敏感)
- DNS 查询(域名解析)
- SNMP(网络管理协议)
- 流媒体(如 IPTV、RTSP 协议)
- 广播通信(如 DHCP 客户端发现服务器)
UDP 编程示例(C 语言)
1.一对一双向通信
客户端代码
/*********************************************************************************
* @Description : 采用UDP协议实现客户端与服务器之间的一对一双向通信
* @Note : 这是客户端的代码,使用虚拟机地址作为客户端地址
* @retval :
* @Author : ice_cui
* @Date : 2025-05-28 13:58:07
* @Email : Lazypanda_ice@163.com
* @LastEditTime : 2025-05-28 15:45:35
* @Version : V1.0.0
* @Copyright : Copyright (c) 2025 Lazypanda_ice@163.com All rights reserved.
**********************************************************************************/
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <netinet/in.h> //inet头文件
#include <netinet/udp.h> //udp头文件
#include <arpa/inet.h> //inet_pton函数
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#define PORT 8888
#define BUFFER_SIZE 1024
char buf[BUFFER_SIZE] = {0};//发送缓冲区
char recv_buf[BUFFER_SIZE] = {0};//接收缓冲区
struct sockaddr_in server_addr, client_addr;
//接收线程函数
void *udp_send(void *arg)
{
int sockfd = *(int *)arg;//创建套接字
while (1)
{
printf("intput the message you want to send:");
scanf("%s",buf);//输入数据
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));//发送数据
bzero(buf,sizeof(buf));//清空缓冲区
}
}
void *udp_recv(void *arg)
{
int sockfd = *(int *)arg;//创建套接字
while (1)
{
recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, NULL, NULL);//接收数据
printf("\nrecv data:%s\n", recv_buf);
bzero(recv_buf,sizeof(recv_buf));//清空缓冲区
}
}
int main(int argc, char const *argv[])
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);//创建套接字
if (sockfd == -1){
//判断套接字是否创建成功
fprintf(stderr,"socket error,errno:%d,%s\n",errno,strerror(errno));
return -1;
}
//设置客户端地址
client_addr.sin_family = AF_INET;//协议族,固定的ipv4
//需要发送数据的服务器端口
client_addr.sin_port = htons(PORT);
//需要发送数据的服务器ip
client_addr.sin_addr.s_addr = inet_addr("192.168.1.11");
bind(sockfd, (struct sockaddr *)&client_addr, sizeof(client_addr));//绑定客户端地址
//设置服务器地址
server_addr.sin_family = AF_INET;//协议族,固定的ipv4
server_addr.sin_port = htons(PORT);//需要发送数据的服务器端口
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//需要发送数据的服务器ip
pthread_t receive_thread, send_thread;
if (pthread_create(&send_thread, NULL, udp_send, &sockfd) != 0)
{
fprintf(stderr,"pthread_create error,errno:%d,%s\n",errno,strerror(errno));
return -1;
}
if (pthread_create(&receive_thread, NULL, udp_recv, &sockfd) != 0)
{
fprintf(stderr,"pthread_create error,errno:%d,%s\n",errno,strerror(errno));
return -1;
}
pthread_join(send_thread, NULL);//等待线程结束
pthread_join(receive_thread, NULL);//等待线程结束
close(sockfd); //关闭套接字
return 0;
}
服务器端代码
/*********************************************************************************
* @Description : 采用UDP协议实现客户端与服务器之间的一对一双向通信
* @Note : 这是服务器端的代码,使用本地环回地址作为服务器地址
* @retval :
* @Author : ice_cui
* @Date : 2025-05-28 13:58:07
* @Email : Lazypanda_ice@163.com
* @LastEditTime : 2025-05-28 15:45:51
* @Version : V1.0.0
* @Copyright : Copyright (c) 2025 Lazypanda_ice@163.com All rights reserved.
**********************************************************************************/
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <netinet/in.h> //inet头文件
#include <netinet/udp.h> //udp头文件
#include <arpa/inet.h> //inet_pton函数
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#define PORT 8888
#define BUFFER_SIZE 1024
char buf[BUFFER_SIZE] = {0};//发送缓冲区
char recv_buf[BUFFER_SIZE] = {0};//接收缓冲区
//需要发送数据的服务器地址
struct sockaddr_in server_addr, client_addr;
//接收线程函数
void *udp_send(void *arg)
{
int sockfd = *(int *)arg;//创建套接字
while (1)
{
printf("intput the message you want to send:");
scanf("%s",buf);//输入数据
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&client_addr, sizeof(client_addr));//发送数据
bzero(buf,sizeof(buf));//清空缓冲区
}
}
void *udp_recv(void *arg)
{
int sockfd = *(int *)arg;//创建套接字
while (1)
{
recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, NULL, NULL);//接收数据
printf("\nrecv data:%s\n", recv_buf);
bzero(recv_buf,sizeof(recv_buf));//清空缓冲区
}
}
int main(int argc, char const *argv[])
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);//创建套接字
if (sockfd == -1){
//判断套接字是否创建成功
fprintf(stderr,"socket error,errno:%d,%s\n",errno,strerror(errno));
return -1;
}
//设置客户端地址
client_addr.sin_family = AF_INET;//协议族,固定的ipv4
//需要发送数据的服务器端口
client_addr.sin_port = htons(PORT);
//需要发送数据的服务器ip
client_addr.sin_addr.s_addr = inet_addr("192.168.1.11");
//设置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//设置服务器地址为本地环回地址
bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));//绑定套接字
pthread_t receive_thread, send_thread;
if (pthread_create(&send_thread, NULL, udp_send, &sockfd) != 0)
{
fprintf(stderr,"pthread_create error,errno:%d,%s\n",errno,strerror(errno));
return -1;
}
if (pthread_create(&receive_thread, NULL, udp_recv, &sockfd) != 0)
{
fprintf(stderr,"pthread_create error,errno:%d,%s\n",errno,strerror(errno));
return -1;
}
pthread_join(send_thread, NULL);//等待线程结束
pthread_join(receive_thread, NULL);//等待线程结束
close(sockfd); //关闭套接字
return 0;
}
2.广播通信
将属性设置为广播,向广播地址发送数据处于与主机处于同一网段下的所有设备都将收到数据
/*********************************************************************************
* @Description : 用于创建一个UDP套接字并检查其广播选项。如果套接字支持广播,则将其设置为广播模式。
* @Note :
* @retval : 返回0表示程序正常结束,返回-1表示程序异常结束。
* @Author : ice_cui
* @Date : 2025-05-28 15:57:33
* @Email : Lazypanda_ice@163.com
* @LastEditTime : 2025-05-28 16:18:48
* @Version : V1.0.0
* @Copyright : Copyright (c) 2025 Lazypanda_ice@163.com All rights reserved.
**********************************************************************************/
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[])
{
// 创建一个UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 如果套接字创建失败,输出错误信息
if (sockfd < 0) {
fprintf(stderr,"socket error\n");
return -1;
}
// 定义一个变量用于存储广播选项的值
int broadcast;
// 定义一个变量用于存储选项的长度
socklen_t optlen = sizeof(broadcast);
// 获取套接字的广播选项值
getsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast, &optlen);
// 输出广播选项的值,如果为1则表示开启广播,否则表示未开启
printf("broadcast: %s\n", broadcast?"yes":"no");
//开启广播
broadcast = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast, optlen);
// 获取套接字的广播选项值
getsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast, &optlen);
// 输出广播选项的值,如果为1则表示开启广播,否则表示未开启
printf("broadcast: %s\n", broadcast?"yes":"no");
// 程序正常结束,返回0
return 0;
}
3.多播/组播通信
一对多通信,接入同一组播地址的设备在发送方发送数据时都能接收到数据。与单播(一对一)和广播(一对所有)相比,组播既能节省带宽,又能灵活控制接收范围,广泛应用于流媒体、视频会议、实时数据同步等场景。
加入多播组接收数据
/*********************************************************************************
* @Description : 创建UDP套接字,加入组播组,并持续接收组播消息。
* @Note :
* @retval : 程序退出状态码
* @Author : ice_cui
* @Date : 2025-05-28 16:24:52
* @Email : Lazypanda_ice@163.com
* @LastEditTime : 2025-05-28 20:56:41
* @Version : V1.0.0
* @Copyright : Copyright (c) 2025 Lazypanda_ice@163.com All rights reserved.
**********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#define PORT 10000
#define GROUPID "239.0.0.1"
#define BUFFER_SIZE 1024
int main(int argc, char const *argv[])
{
struct sockaddr_in multicast_addr, local_addr; // 定义组播地址与本地地址
struct ip_mreq mreq;
char buffer[BUFFER_SIZE] = {0};
// 设置套接字选项
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
perror("socket");
exit(1);
}
// 设置本地地址
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = INADDR_ANY;
local_addr.sin_port = htons(PORT);
bind(sock, (struct sockaddr *)&local_addr, sizeof(local_addr)); // 绑定本地地址
// 设置组播地址
mreq.imr_multiaddr.s_addr = inet_addr(GROUPID);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); // 加入组播组
while (1)
{
recvfrom(sock, buffer, BUFFER_SIZE, 0, NULL, NULL);
printf("Received message: %s\n", buffer);
bzero(buffer, BUFFER_SIZE);
}
return 0;
}
向多播地址发送数据,处于多播组中的成员都可以接收到数据
/*********************************************************************************
* @Description : 创建UDP套接字并加入组播组,然后周期性地向组播地址发送消息。
* @Note :
* @retval : 程序退出状态码
* @Author : ice_cui
* @Date : 2025-05-28 16:24:52
* @Email : Lazypanda_ice@163.com
* @LastEditTime : 2025-05-28 20:58:45
* @Version : V1.0.0
* @Copyright : Copyright (c) 2025 Lazypanda_ice@163.com All rights reserved.
**********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#define PORT 10000
#define GROUPID "239.0.0.1"
#define MESSAGE "Hello Group"
int main(int argc, char const *argv[])
{
struct sockaddr_in group_addr, local_addr; // 定义组播地址与本地地址
// 创建UDP套接字
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
perror("socket"); // 打印套接字创建错误信息
exit(1); // 退出程序
}
// 设置组播地址
group_addr.sin_family = AF_INET; // 设置地址族为IPv4
group_addr.sin_addr.s_addr = inet_addr(GROUPID); // 设置组播IP地址
group_addr.sin_port = htons(PORT); // 设置端口号,并转换为网络字节序
inet_pton(AF_INET, GROUPID, &group_addr.sin_addr.s_addr); // 将点分十进制IP地址转换为二进制IP地址
// 循环发送消息
while (1)
{
sendto(sock, MESSAGE, strlen(MESSAGE), 0, (struct sockaddr *)&group_addr, sizeof(group_addr)); // 向组播地址发送消息
sleep(5); // 暂停5秒
}
close(sock); // 关闭套接字
return 0; // 程序正常退出
}
4.域名解析
/*********************************************************************************
* @Description : 获取域名对应的IP地址并尝试连接
* @Note :
* @retval : 程序退出状态码
* @Author : ice_cui
* @Date : 2025-05-28 17:12:45
* @Email : Lazypanda_ice@163.com
* @LastEditTime : 2025-05-28 17:41:37
* @Version : V1.0.0
* @Copyright : Copyright (c) 2025 Lazypanda_ice@163.com All rights reserved.
**********************************************************************************/
#include <stdio.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#define PORT 80
#define DOMAIN "www.baidu.com"
int main(int argc, char const *argv[])
{
struct hostent *name; // 获取域名信息
struct in_addr **addr_list; // 获取域名对应的IP地址列表
int count = 0; // 成功连接的IP地址计数
// 获取指定域名的信息
name = gethostbyname(DOMAIN);
if (name == NULL) {
herror("gethostbyname"); // 如果获取失败,输出错误信息
exit(1); // 退出程序
}
// 获取域名对应的IP地址列表
addr_list = (struct in_addr **)name->h_addr_list;
// 遍历IP地址列表
for (int i = 0; addr_list[i] != NULL; i++) {
// 打印当前IP地址
printf("IP address %d: %s\n", i, inet_ntoa(*addr_list[i]));
// 创建socket
int socketfd = socket(AF_INET, SOCK_DGRAM, 0);
if (socketfd == -1) {
fprintf(stderr,"socket error: %s\n", strerror(errno)); // 如果创建失败,输出错误信息
exit(1); // 退出程序
}
// 初始化服务器地址结构
struct sockaddr_in server_addr; // 服务器地址
server_addr.sin_family = AF_INET; // IPv4
server_addr.sin_port = htons(PORT); // 端口号
server_addr.sin_addr = *addr_list[i]; // IP地址
// 尝试连接服务器
if (connect(socketfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
{
printf("[%s]connect error\n",inet_ntoa(*addr_list[i])); // 如果连接失败,输出错误信息
close(socketfd); // 关闭socket
}else{
count++; // 成功连接计数加1
printf("[%s]connect success\n",inet_ntoa(*addr_list[i])); // 如果连接成功,输出成功信息
close(socketfd); // 关闭socket
}
printf("the ip is number of:%d\n",count); // 输出成功连接的IP地址数量
}
// 如果没有成功连接的IP地址
if (count == 0) {
printf("No IP addresses found for %s\n", DOMAIN); // 输出提示信息
}
return 0; // 程序正常退出
}
UDP 与 TCP 的对比
特性 | UDP | TCP |
---|---|---|
连接性 | 无连接 | 面向连接(三次握手) |
可靠性 | 不可靠 | 可靠(重传、排序) |
传输效率 | 高(开销小) | 低(协议开销大) |
应用场景 | 实时音视频、游戏、DNS | 文件传输、网页浏览、邮件 |
拥塞控制 | 无 | 有(慢启动、拥塞避免) |
传输单位 | 数据报(Datagram | 字节流(Byte Stream) |