铜川市网站建设_网站建设公司_PHP_seo优化
2025/12/24 16:28:12 网站建设 项目流程

UDP Socket 编程笔记

一、UDP 基础知识

1. UDP 特点

  • 无连接:无需建立连接即可通信

  • 不可靠:不保证数据到达、不保证顺序

  • 面向数据报:有明确的报文边界

  • 高效:开销小,速度快

2. TCP vs UDP

特性TCPUDP
连接方式面向连接无连接
可靠性可靠传输不可靠
数据边界流式(无边界)数据报(有边界)
速度较慢较快
头部大小20-60字节8字节

3. 核心数据结构(同TCP)

struct sockaddr_in { short sin_family; // AF_INET unsigned short sin_port; // 端口号 struct in_addr sin_addr; // IP地址 char sin_zero[8]; // 填充 };

总结:UDP编程相对TCP更简单,但需要应用层处理可靠性问题。适合实时性要求高、可容忍少量丢失的场景(如视频流、DNS查询、在线游戏等)。

二、UDP 编程核心函数

1. 发送数据

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
  • dest_addr: 目标地址结构体

  • addrlen: 地址结构体长度

  • 返回:实际发送的字节数,-1表示错误

2. 接收数据

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
  • src_addr: 用于保存发送方地址

  • addrlen: 输入输出参数,需要初始化

  • 返回:实际接收的字节数,-1表示错误,0表示对方关闭连接

三、示例程序分析

示例1:简单回显服务器(带时间戳)

服务器端 (server.c)
#include <arpa/inet.h> #include <netinet/in.h> #include <netinet/ip.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> typedef struct sockaddr *(SA); int main(int argc, char **argv) { // 1. 创建UDP套接字 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 2. 绑定地址 struct sockaddr_in ser, cli; bzero(&ser, sizeof(ser)); bzero(&cli, sizeof(cli)); ser.sin_family = AF_INET; ser.sin_port = htons(50000); ser.sin_addr.s_addr = inet_addr("192.168.14.128"); bind(sockfd, (SA)&ser, sizeof(ser)); // 3. 循环处理客户端请求 socklen_t len = sizeof(cli); while (1) { char buf[512] = {0}; // 接收数据(获取客户端地址) recvfrom(sockfd, buf, sizeof(buf), 0, (SA)&cli, &len); printf("recv:%s\n", buf); // 添加时间戳 time_t tm; time(&tm); struct tm *info = localtime(&tm); sprintf(buf, "%s %d:%d:%d", buf, info->tm_hour, info->tm_min, info->tm_sec); // 发送回客户端 sendto(sockfd, buf, strlen(buf) + 1, 0, (SA)&cli, len); } close(sockfd); return 0; }
客户端 (cli.c)
#include <arpa/inet.h> #include <netinet/in.h> #include <netinet/ip.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> typedef struct sockaddr *(SA); int main(int argc, char **argv) { // 1. 创建UDP套接字 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 2. 设置服务器地址 struct sockaddr_in ser; bzero(&ser, sizeof(ser)); ser.sin_family = AF_INET; ser.sin_port = htons(50000); ser.sin_addr.s_addr = inet_addr("192.168.14.128"); // 3. 发送10次数据 int i = 10; while (i--) { char buf[512] = "hello"; // 发送数据 sendto(sockfd, buf, strlen(buf), 0, (SA)&ser, sizeof(ser)); // 接收响应 bzero(buf, sizeof(buf)); recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL); printf("recv:%s\n", buf); sleep(1); } close(sockfd); return 0; }

示例2:UDP聊天程序(父子进程)

服务器端 (server.c - 聊天版本)
// 创建UDP套接字并绑定 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); struct sockaddr_in ser, cli; // ... 绑定代码 ... // 首次接收获取客户端地址 char buf[512] = {0}; recvfrom(sockfd, buf, sizeof(buf), 0, (SA)&cli, &len); // 创建父子进程 pid_t pid = fork(); if (pid > 0) { // 父进程:发送消息 while (1) { bzero(buf, sizeof(buf)); printf("to B:"); fgets(buf, sizeof(buf), stdin); sendto(sockfd, buf, strlen(buf) + 1, 0, (SA)&cli, sizeof(cli)); if (0 == strcmp(buf, "#quit\n")) { kill(pid, 9); exit(0); } } } else if (0 == pid) { // 子进程:接收消息 while (1) { bzero(buf, sizeof(buf)); recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL); if (0 == strcmp(buf, "#quit\n")) { kill(getppid(), 9); exit(0); } printf("from B:%s", buf); fflush(stdout); } }
客户端 (cli.c - 聊天版本)
// 代码结构与服务器端类似 // 父子进程分别处理发送和接收

实现原理

  1. 父子进程分离了输入和输出

  2. 父进程负责读取用户输入并发送

  3. 子进程负责接收并显示消息

  4. 使用#quit作为退出指令

示例3:UDP文件传输

服务器端 (文件接收)
int fd = open("2.png", O_WRONLY | O_CREAT | O_TRUNC, 0666); while (1) { char buf[1024] = {0}; int rd_ret = recvfrom(sockfd, buf, sizeof(buf), 0, (SA)&cli, &len); if (rd_ret <= 0) { break; } write(fd, buf, rd_ret); // 发送确认 bzero(buf, sizeof(buf)); strcpy(buf, "go on"); sendto(sockfd, buf, strlen(buf), 0, (SA)&cli, len); }
客户端 (文件发送)
int fd = open("/home/linux/1.png", O_RDONLY); char buf[1024] = {0}; while (1) { bzero(buf, sizeof(buf)); int rd_ret = read(fd, buf, sizeof(buf)); if (rd_ret <= 0) { break; } // 发送文件数据 sendto(sockfd, buf, rd_ret, 0, (SA)&ser, sizeof(ser)); // 等待确认 bzero(buf, sizeof(buf)); recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL); } // 发送结束标志 sendto(sockfd, buf, 0, 0, (SA)&ser, sizeof(ser));

特点

  • 使用固定大小缓冲区传输

  • 简单的"go on"确认机制

  • 发送0长度数据表示传输结束

四、UDP编程注意事项

1. 地址绑定

// 监听所有接口 ser.sin_addr.s_addr = INADDR_ANY; // 监听特定IP ser.sin_addr.s_addr = inet_addr("192.168.1.100"); // 仅本地通信(回环地址) ser.sin_addr.s_addr = inet_addr("127.0.0.1");

2. 数据包大小限制

  • UDP数据包最大长度:65535字节

  • 实际受MTU限制(通常1500字节)

  • 建议应用层分片传输大文件

3. 可靠性问题解决方案

// 1. 超时重传 struct timeval tv; tv.tv_sec = 5; // 5秒超时 tv.tv_usec = 0; setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); // 2. 序列号机制 typedef struct { uint32_t seq; // 序列号 uint32_t total; // 总包数 char data[1400]; // 数据 } udp_packet_t;

4. 并发处理

UDP本身是无连接的,可以通过以下方式处理多个客户端:

  • 记录每个客户端的地址

  • 使用多线程/多进程

  • 使用select()/poll()/epoll()多路复用

五、完整编程模板

服务器模板

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define PORT 50000 #define BUFFER_SIZE 1024 int main() { int sockfd; struct sockaddr_in server_addr, client_addr; socklen_t client_len; char buffer[BUFFER_SIZE]; // 1. 创建套接字 sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 2. 配置服务器地址 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = INADDR_ANY; // 3. 绑定地址 bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); printf("UDP Server listening on port %d\n", PORT); while (1) { // 4. 接收数据 client_len = sizeof(client_addr); int n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&client_addr, &client_len); // 5. 处理数据 buffer[n] = '\0'; printf("Received from %s:%d - %s\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buffer); // 6. 发送响应 sendto(sockfd, buffer, n, 0, (struct sockaddr*)&client_addr, client_len); } close(sockfd); return 0; }

客户端模板

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define SERVER_IP "127.0.0.1" #define PORT 50000 #define BUFFER_SIZE 1024 int main() { int sockfd; struct sockaddr_in server_addr; char buffer[BUFFER_SIZE]; // 1. 创建套接字 sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 2. 配置服务器地址 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); while (1) { printf("Enter message: "); fgets(buffer, BUFFER_SIZE, stdin); // 3. 发送数据 sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr*)&server_addr, sizeof(server_addr)); // 4. 接收响应 int n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, NULL, NULL); buffer[n] = '\0'; printf("Server response: %s\n", buffer); } close(sockfd); return 0; }

六、常见错误及解决方法

1. 地址已在使用

# 错误:bind: Address already in use # 解决: netstat -anp | grep 50000 # 查看占用进程 kill -9 <PID> # 结束进程 # 或使用 SO_REUSEADDR int opt = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

2. 数据包丢失

  • 实现应用层的确认重传机制

  • 增加超时设置

  • 使用更小的数据包大小

3. 端口选择问题

  • 避免使用知名端口(0-1023)

  • 确保客户端和服务器使用相同端口

七、进阶主题

1. 广播通信

// 设置广播权限 int broadcast = 1; setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)); // 广播地址 ser.sin_addr.s_addr = inet_addr("255.255.255.255");

2. 组播通信

// 加入组播组 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));

3. 非阻塞UDP

// 设置非阻塞模式 int flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询