Day34 UDP套接字编程 可靠文件传输与实时双向聊天系统
day34 UDP套接字编程 可靠文件传输与实时双向聊天系统
UDP文件传输
实现客户端向服务器传输文件(如图片)的功能,确保传输后文件内容完全一致且可正常打开。传输过程采用简单的确认机制防止数据包丢失,传输完成后双方程序自动退出。核心知识点包括:UDP套接字创建、文件读写、数据包分块传输、传输结束标志处理。
客户端代码 (cli.c)
#include <arpa/inet.h> // 提供IP地址转换函数
#include <fcntl.h> // 提供文件控制操作
#include <netinet/in.h> // 定义IPv4地址结构
#include <netinet/ip.h> // 定义IP协议相关结构
#include <stdio.h> // 标准输入输出函数
#include <stdlib.h> // 标准库函数
#include <string.h> // 字符串操作函数
#include <sys/socket.h> // 套接字API
#include <sys/types.h> // 数据类型定义
#include <time.h> // 时间相关函数
#include <unistd.h> // POSIX系统调用typedef struct sockaddr *(SA); // 定义sockaddr指针别名,简化类型转换int main(int argc, char **argv)
{// 创建UDP套接字(AF_INET: IPv4, SOCK_DGRAM: 数据报套接字)int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sockfd){perror("socket"); // 套接字创建失败时打印错误return 1;}// 配置服务器地址结构struct sockaddr_in ser;ser.sin_family = AF_INET; // 地址族:IPv4ser.sin_port = htons(50000); // 端口号转网络字节序(50000)ser.sin_addr.s_addr = inet_addr("192.168.1.48"); // 服务器IP地址// 打开本地文件(只读模式)int fd = open("/home/linux/1.png", O_RDONLY);if (-1 == fd){perror("open error\n");return 1;}int num = 0; // 累计已发送字节数char buf[1024] = {0}; // 数据缓冲区(1024字节块)while (1){bzero(buf, sizeof(buf)); // 清空缓冲区int ret = read(fd, buf, sizeof(buf)); // 从文件读取数据到缓冲区num += ret; // 累计传输字节数printf("num:%d\n", num); // 实时打印已发送字节数// 读取结束或出错时退出循环if (ret <= 0){break;}// 向服务器发送数据包sendto(sockfd, buf, ret, 0, (SA)&ser, sizeof(ser));// 接收服务器确认包(小阻塞:确保服务器处理完当前包)bzero(buf, sizeof(buf));recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);}// 发送传输结束标志strcpy(buf, "^_^"); // 结束标志字符串sendto(sockfd, buf, strlen(buf), 0, (SA)&ser, sizeof(ser));// 关闭资源close(sockfd);close(fd);return 0;
}
服务器代码 (ser.c)
#include <arpa/inet.h> // 提供IP地址转换函数
#include <netinet/in.h> // 定义IPv4地址结构
#include <netinet/ip.h> // 定义IP协议相关结构
#include <stdio.h> // 标准输入输出函数
#include <stdlib.h> // 标准库函数
#include <string.h> // 字符串操作函数
#include <sys/socket.h> // 套接字API
#include <sys/types.h> // 数据类型定义
#include <unistd.h> // POSIX系统调用
#include <time.h> // 时间相关函数
#include <fcntl.h> // 提供文件控制操作typedef struct sockaddr * (SA); // 定义sockaddr指针别名int main(int argc, char **argv)
{// 创建UDP套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sockfd){perror("socket");return 1;}// 配置服务器地址结构(用于绑定)struct sockaddr_in ser, cli;ser.sin_family = AF_INET; // 地址族:IPv4ser.sin_port = htons(50000); // 端口号转网络字节序ser.sin_addr.s_addr = inet_addr("192.168.1.48"); // 本机IP地址// 绑定套接字到指定地址和端口int ret = bind(sockfd, (SA) &ser, sizeof(ser));if (-1 == ret){perror("bind");return 1;}time_t tm; // 时间变量(未实际使用,保留原代码)socklen_t len = sizeof(cli); // 客户端地址结构长度// 创建新文件(写入/创建/截断模式,权限0666)int fd = open("2.png", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (-1 == fd){perror("open error\n");return 1;}int num = 0; // 累计接收字节数while (1){char buf[1024] = {0}; // 数据缓冲区// 接收客户端数据包int ret = recvfrom(sockfd, buf, sizeof(buf), 0, (SA)&cli, &len);num += ret; // 累计接收字节数printf("num:%d\n", num); // 实时打印已接收字节数// 检测到结束标志时退出循环if (0 == strcmp(buf, "^_^")){break;}// 将数据写入文件write(fd, buf, ret);// 发送确认包(解除客户端阻塞)bzero(buf, sizeof(buf));strcpy(buf, "go on"); // 确认消息sendto(sockfd, buf, strlen(buf), 0, (SA)&cli, len);}// 关闭资源close(sockfd);close(fd);return 0;
}
理想运行结果
- 文件传输过程:
- 客户端输出:
num:1024
→num:2048
→ … →num:[文件总大小]
(字节数递增) - 服务器输出:
num:1024
→num:2048
→ … →num:[文件总大小]
(与客户端一致)
- 客户端输出:
- 传输验证:
- 传输完成后,
2.png
与原始1.png
大小完全相同(ls -l
对比) - 图片文件可正常打开(如
eog 2.png
无损坏)
- 传输完成后,
- 退出机制:
- 客户端发送
^_^
后自动退出 - 服务器收到
^_^
后自动退出
- 客户端发送
- 关键特性:
- 每个数据包传输后通过
recvfrom
/sendto
实现简单确认,避免发送过快导致丢包 - 结束标志
^_^
确保双方同步退出
- 每个数据包传输后通过
UDP聊天
实现双向实时聊天功能,支持连续消息收发。输入 #quit
时,客户端和服务器同时退出。核心知识点包括:多进程并发处理(fork
)、消息循环控制、退出信号同步。
服务器代码 (ser.c)
#include <arpa/inet.h> // IP地址转换
#include <fcntl.h> // 文件控制
#include <netinet/in.h> // IPv4地址结构
#include <netinet/ip.h> // IP协议结构
#include <signal.h> // 信号处理(用于进程终止)
#include <stdio.h> // 标准I/O
#include <stdlib.h> // 标准库
#include <string.h> // 字符串操作
#include <sys/socket.h> // 套接字API
#include <sys/types.h> // 数据类型
#include <time.h> // 时间函数
#include <unistd.h> // POSIX系统调用typedef struct sockaddr *(SA); // sockaddr指针别名int main(int argc, char **argv)
{// 创建UDP套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sockfd){perror("socket");return 1;}// 配置服务器地址结构struct sockaddr_in ser, cli;ser.sin_family = AF_INET;ser.sin_port = htons(50000); // 监听端口50000ser.sin_addr.s_addr = inet_addr("192.168.1.48"); // 本机IP// 绑定套接字int ret = bind(sockfd, (SA)&ser, sizeof(ser));if (-1 == ret){perror("bind");return 1;}socklen_t len = sizeof(cli); // 客户端地址长度char buf[512] = {0}; // 消息缓冲区// 等待客户端初始连接("start"消息)recvfrom(sockfd, buf, sizeof(buf), 0, (SA)&cli, &len);// 创建子进程处理并发pid_t pid = fork();if (pid > 0) // 父进程:接收客户端消息{while (1){bzero(buf, sizeof(buf)); // 清空缓冲区// 接收客户端消息recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);// 检测退出命令if (0 == strcmp(buf, "#quit\n")){kill(pid, 9); // 终止子进程exit(1); // 父进程退出}printf("cli:%s", buf); // 显示客户端消息}}else if (0 == pid) // 子进程:发送服务器消息{while (1){printf("to cli:"); // 提示输入char buf[512] = {0};fgets(buf, sizeof(buf), stdin); // 读取用户输入// 发送消息到客户端sendto(sockfd, buf, strlen(buf), 0, (SA)&cli, len);// 检测退出命令if (0 == strcmp(buf, "#quit\n")){kill(getppid(), 9); // 终止父进程exit(1); // 子进程退出}}}else // fork失败{perror("fork");return 1;}close(sockfd); // 关闭套接字(实际不会执行到此处)return 0;
}
客户端代码 (cli.c)
#include <arpa/inet.h> // IP地址转换
#include <netinet/in.h> // IPv4地址结构
#include <netinet/ip.h> // IP协议结构
#include <stdio.h> // 标准I/O
#include <stdlib.h> // 标准库
#include <string.h> // 字符串操作
#include <sys/socket.h> // 套接字API
#include <sys/types.h> // 数据类型
#include <unistd.h> // POSIX系统调用
#include <time.h> // 时间函数
#include <fcntl.h> // 文件控制
#include <signal.h> // 信号处理typedef struct sockaddr * (SA); // sockaddr指针别名int main(int argc, char **argv)
{// 创建UDP套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sockfd){perror("socket");return 1;}// 配置服务器地址结构struct sockaddr_in ser;ser.sin_family = AF_INET;ser.sin_port = htons(50000); // 目标端口50000ser.sin_addr.s_addr = inet_addr("192.168.1.48"); // 服务器IPsocklen_t len = sizeof(ser); // 地址长度char buf[512] = {0};strcpy(buf, "start"); // 初始连接消息sendto(sockfd, buf, strlen(buf), 0, (SA)&ser, len); // 通知服务器// 创建子进程处理并发pid_t pid = fork();if (pid > 0) // 父进程:接收服务器消息{while (1){bzero(buf, sizeof(buf));// 接收服务器消息recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);// 检测退出命令if (0 == strcmp(buf, "#quit\n")){kill(pid, 9); // 终止子进程exit(1); // 父进程退出}printf("ser:%s", buf); // 显示服务器消息}}else if (0 == pid) // 子进程:发送客户端消息{while (1){printf("to ser:"); // 提示输入char buf[512] = {0};fgets(buf, sizeof(buf), stdin); // 读取用户输入// 发送消息到服务器sendto(sockfd, buf, strlen(buf), 0, (SA)&ser, len);// 检测退出命令if (0 == strcmp(buf, "#quit\n")){kill(getppid(), 9); // 终止父进程exit(1); // 子进程退出}}}else // fork失败{perror("fork");return 1;}close(sockfd); // 关闭套接字(实际不会执行到此处)return 0;
}
理想运行结果
-
初始化连接:
- 客户端发送
start
消息,服务器开始监听 - 双方进入消息循环
- 客户端发送
-
聊天过程:
- 服务器终端:
to cli:Hello // 服务器输入 cli:How are you? // 显示客户端消息 to cli:#quit // 输入退出命令
- 客户端终端:
ser:Hello // 显示服务器消息 to ser:How are you? // 客户端输入 ser:#quit // 收到退出命令
- 服务器终端:
-
退出机制:
- 任意一方输入
#quit
后:- 发送方立即终止对方进程(
kill
) - 本地进程退出(
exit(1)
)
- 发送方立即终止对方进程(
- 双方终端同时关闭,无残留进程
- 任意一方输入
-
关键特性:
- 通过
fork
实现双工通信:父进程处理接收,子进程处理发送 - 退出命令
#quit
触发双向终止(避免单方退出导致僵局) - 消息实时显示(
printf
前无缓冲,即时刷新)
- 通过