UDP(User Datagram Protocol)是一种无连接的传输层协议,提供简单、高效的数据传输服务。
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 特性 | UDP | TCP |
|---|---|---|
| 连接方式 | 无连接 | 面向连接 |
| 可靠性 | 不可靠 | 可靠 |
| 传输单位 | 数据报 | 字节流 |
| 头部大小 | 8字节 | 20-60字节 |
| 传输效率 | 高 | 较低 |
| 流量控制 | 无 | 有 |
| 拥塞控制 | 无 | 有 |
| 广播/组播 | 支持 | 不支持 |
| 适用场景 | 实时应用、DNS、DHCP | 文件传输、网页、邮件 |
socket() → bind() → recvfrom()/sendto() → close()
int socket(int domain, int type, int protocol);
domain:AF_INET(IPv4)或AF_INET6(IPv6)type:SOCK_DGRAM(数据报套接字)protocol:0(自动选择协议)int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
绑定本地地址和端口,客户端可以不绑定(系统自动分配)。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
sockfd:套接字描述符buf:接收缓冲区len:缓冲区大小flags:标志位(通常为0)src_addr:发送方地址(输出参数)addrlen:地址结构体大小(输入输出参数)ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
dest_addr:目的地址addrlen:地址结构体大小socket() → [bind()] → sendto() → recvfrom() → close()
客户端通常不需要bind,系统会自动分配端口。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
#define PORT 8888
int main() {
int server_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
char buffer[BUF_SIZE];
ssize_t recv_len;
// 1. 创建UDP套接字
server_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (server_fd < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 2. 绑定地址和端口
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("UDP Echo server listening on port %d\n", PORT);
// 3. 循环接收和发送数据
while (1) {
client_len = sizeof(client_addr);
recv_len = recvfrom(server_fd, buffer, BUF_SIZE, 0,
(struct sockaddr *)&client_addr, &client_len);
if (recv_len < 0) {
perror("recvfrom failed");
continue;
}
printf("Received from %s:%d: %.*s\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port),
(int)recv_len, buffer);
// 回显数据
if (sendto(server_fd, buffer, recv_len, 0,
(struct sockaddr *)&client_addr, client_len) < 0) {
perror("sendto failed");
}
}
close(server_fd);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888
int main() {
int client_fd;
struct sockaddr_in server_addr;
socklen_t server_len;
char buffer[BUF_SIZE];
ssize_t recv_len;
// 1. 创建UDP套接字
client_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (client_fd < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 2. 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
perror("inet_pton failed");
close(client_fd);
exit(EXIT_FAILURE);
}
// 3. 发送数据
printf("Enter message: ");
fgets(buffer, BUF_SIZE, stdin);
buffer[strcspn(buffer, "\n")] = 0; // 移除换行符
if (sendto(client_fd, buffer, strlen(buffer), 0,
(struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("sendto failed");
close(client_fd);
exit(EXIT_FAILURE);
}
// 4. 接收回显数据
server_len = sizeof(server_addr);
recv_len = recvfrom(client_fd, buffer, BUF_SIZE, 0,
(struct sockaddr *)&server_addr, &server_len);
if (recv_len < 0) {
perror("recvfrom failed");
} else {
buffer[recv_len] = '\0';
printf("Echo received: %s\n", buffer);
}
close(client_fd);
return 0;
}
# 编译服务器
gcc -o udp_server udp_server.c
# 编译客户端
gcc -o udp_client udp_client.c
# 运行服务器
./udp_server
# 运行客户端(另一个终端)
./udp_client
struct sockaddr_in {
short sin_family; // AF_INET
unsigned short sin_port; // 端口号
struct in_addr sin_addr; // IP地址
char sin_zero[8]; // 填充字节
};
struct in_addr {
in_addr_t s_addr; // 网络字节序的IP地址
};
// 点分十进制 → 网络字节序
int inet_pton(int af, const char *src, void *dst);
// 网络字节序 → 点分十进制
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
// 主机字节序 → 网络字节序
uint16_t htons(uint16_t hostshort);
uint32_t htonl(uint32_t hostlong);
// 网络字节序 → 主机字节序
uint16_t ntohs(uint16_t netshort);
uint32_t ntohl(uint32_t netlong);
recvfrom的src_addr参数是输出参数,返回发送方地址sendto的dest_addr参数是输入参数,指定目的地址sockaddr_in,IPv6使用sockaddr_in6// 设置广播属性
int broadcast = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
// 发送广播消息
struct sockaddr_in broadcast_addr;
broadcast_addr.sin_family = AF_INET;
broadcast_addr.sin_port = htons(8888);
broadcast_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
sendto(sockfd, message, strlen(message), 0,
(struct sockaddr *)&broadcast_addr, sizeof(broadcast_addr));
// 加入组播组
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.1");
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
// 发送组播消息
struct sockaddr_in multicast_addr;
multicast_addr.sin_family = AF_INET;
multicast_addr.sin_port = htons(8888);
multicast_addr.sin_addr.s_addr = inet_addr("224.0.0.1");
sendto(sockfd, message, strlen(message), 0,
(struct sockaddr *)&multicast_addr, sizeof(multicast_addr));
UDP本身不可靠,需要在应用层实现:
sendmmsg/recvmmsg批量发送writev/readv:分散/聚集IOSO_SNDBUF和SO_RCVBUFaddrlen参数传递错误EAGAIN:非阻塞模式下需要处理UDP和TCP的区别是什么?
UDP服务器为什么不需要listen和accept?
如何保证UDP传输的可靠性?
UDP的适用场景有哪些?
如何实现UDP的广播和组播?
SO_BROADCAST属性,使用特定的组播地址recvfrom和sendto的addr参数含义htons、htonl、ntohs、ntohl