Linux网络编程基础(UDP socket编程)
UDP协议是一种无连接的传输层协议,具有低延迟、简单高效的特点,适用于实时应用场景。摘要要点:1)UDP核心特性包括无连接、不可靠传输、面向数据报和低延迟;2)编程模型简单,服务器端只需创建套接字、绑定地址和收发数据;3)关键API包括socket()、bind()、sendto()和recvfrom();4)与TCP相比,UDP不保证可靠性但效率更高;5)适用于流媒体、在线游戏等对实时性要求高的
UDP(用户数据报协议)是一种无连接的传输层协议,与TCP不同,它不保证数据包的顺序和可靠性,但其简单性和低延迟特性使其在实时应用中非常有用。
一、UDP协议核心特性
UDP作为传输层协议,与TCP的“可靠连接”不同,它具有以下特点:
- 无连接:通信前无需建立三次握手,直接发送数据报
- 不可靠:不保证数据有序到达,也不重传丢失的数据包
- 面向数据报:数据以固定大小的“数据报”为单位传输
- 低延迟:省去连接建立和确认机制,适合实时场景(如聊天、直播)
UDP适合用于一次传送少量数据、对可靠性要求不高的应用环境。例如流媒体、在线游戏、DNS查询等场景。
二、UDP编程模型
UDP是无连接的,先启动哪一端都不会报错。UDP编程与TCP的核心差异在于“无连接”特性——无需listen、accept、connect流程,直接通过sendto与recvfrom完成数据收发。
服务器端流程:
- 创建套接字:使用
socket(AF_INET, SOCK_DGRAM, 0)创建UDP套接字 - 绑定地址:使用
bind()将套接字与IP地址和端口号关联 - 收发数据:使用
recvfrom()/sendto()进行数据交互 - 关闭套接字:使用
close()释放资源
客户端流程:
- 创建套接字:使用
socket(AF_INET, SOCK_DGRAM, 0)创建UDP套接字 - 收发数据:使用
sendto()/recvfrom()进行数据交互 - 关闭套接字:使用
close()释放资源
三、核心API函数详解
1. socket() - 创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
AF_INET:IPv4协议族SOCK_DGRAM:数据报套接字,对应UDP协议,区别于TCP的SOCK_STREAM- 第三个参数通常设为0,表示由内核自动选择协议
2. bind() - 绑定地址(服务器端必须)
UDP服务器端必须调用bind函数将套接字绑定到固定的本地端口——客户端需要通过该端口定位服务器,而UDP客户端通常无需绑定(系统自动分配临时端口)。
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(12345);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
3. sendto() - 发送数据
UDP无连接特性决定了其发送数据时需明确指定目标地址——sendto函数正是为此设计,它将数据报发送到指定的服务器端地址,无需提前建立连接(区别于TCP的send)。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
重要说明:sendto返回成功仅表示数据已交给内核发送缓冲区,不保证对方能接收(UDP无确认机制),这与TCP的send有本质区别。
4. recvfrom() - 接收数据
UDP服务器端通过recvfrom接收客户端发送的数据报,同时获取发送方(客户端)的地址信息,以便后续回复。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
阻塞特性:默认情况下,若套接字无数据可接收,recvfrom会一直阻塞,直到有数据报到达或被信号中断。
5. close() - 关闭套接字
close(sockfd);
四、完整代码示例
UDP服务器端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define SERVER_PORT 8888
#define BUFFER_SIZE 1024
int main() {
int server_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
// 1. 创建UDP套接字
if ((server_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation 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(SERVER_PORT);
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("UDP Server listening on port %d...\n", SERVER_PORT);
// 3. 循环收发数据
while (1) {
memset(buffer, 0, BUFFER_SIZE);
// 接收客户端数据
ssize_t recv_len = recvfrom(server_fd, buffer, BUFFER_SIZE - 1, 0,
(struct sockaddr *)&client_addr, &client_addr_len);
if (recv_len < 0) {
perror("recvfrom failed");
continue;
}
buffer[recv_len] = '\0';
printf("Received from %s:%d: %s\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port),
buffer);
// 回显数据到客户端
sendto(server_fd, buffer, strlen(buffer), 0,
(struct sockaddr *)&client_addr, client_addr_len);
}
close(server_fd);
return 0;
}
UDP客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define SERVER_PORT 8888
#define BUFFER_SIZE 1024
int main() {
int client_fd;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE];
// 1. 创建UDP套接字
if ((client_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation 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);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
// 3. 发送数据
const char *message = "Hello, UDP Server!";
sendto(client_fd, message, strlen(message), 0,
(struct sockaddr *)&server_addr, sizeof(server_addr));
printf("Sent: %s\n", message);
// 4. 接收回显数据
socklen_t addr_len = sizeof(server_addr);
ssize_t recv_len = recvfrom(client_fd, buffer, BUFFER_SIZE - 1, 0,
(struct sockaddr *)&server_addr, &addr_len);
if (recv_len > 0) {
buffer[recv_len] = '\0';
printf("Received: %s\n", buffer);
}
close(client_fd);
return 0;
}
五、UDP与TCP编程的关键差异
| 维度 | UDP | TCP |
|---|---|---|
| 连接方式 | 无连接,直接发送数据报 | 面向连接,需三次握手建立连接 |
| 可靠性 | 不可靠,不保证数据到达 | 可靠,有确认重传机制 |
| 数据边界 | 保留消息边界 | 流式协议,无消息边界(需自行处理粘包) |
| 服务端流程 | socket→bind→recvfrom/sendto | socket→bind→listen→accept→recv/send |
| 客户端流程 | socket→sendto/recvfrom | socket→connect→send/recv |
| 适用场景 | 实时音视频、在线游戏、DNS查询 | 文件传输、网页浏览、邮件服务 |
六、错误处理
所有系统调用都可能出错,合适的错误处理能提高程序的健壮性。使用errno来检查错误代码,并利用perror()或strerror()来输出或处理错误信息。
常见错误处理示例:
if (sendto(sockfd, message, strlen(message), 0,
(struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) {
perror("sendto failed");
// 根据errno进行相应处理
}
七、UDP编程的注意事项
-
数据报大小限制:UDP数据报最大64KB(包括头部),建议发送数据控制在1472字节以内(以太网MTU 1500 - IP头部20 - UDP头部8)以避免分片。
-
UDP套接字没有粘包问题,但是不能替代TCP套接字,因为UDP协议有一个缺陷:如果数据发送的途中丢失,则数据就丢失了,而TCP则不会有这种缺陷。
-
空数据的处理:UDP协议是数据报协议,传入的数据可为空,在传输过程中UDP会对数据进行内部的拼接和处理。
-
断开链接的影响:UDP协议是通过解析对方数据中的IP和端口来返回数据的,所以一方发生问题并不会影响到另一方。
八、进阶应用场景
基于UDP可以构建多种实际应用,例如:
- 回显服务器:接收客户端消息并原样返回,是UDP编程的“Hello World”
- 英译汉字典服务:客户端发送英文单词,服务器返回中文释义
- 多线程聊天室:使用多线程处理多个客户端,实现实时群聊功能
这些应用展示了UDP在实际开发中的灵活性和实用性,掌握基础后可以进一步探索更复杂的网络应用开发。处理大量并发客户端时,可能需要使用多线程(pthread库)或多进程(fork系统调用)来实现。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)