当前位置: 首页 > ds >正文

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;
}

理想运行结果

  1. 文件传输过程
    • 客户端输出:num:1024num:2048 → … → num:[文件总大小](字节数递增)
    • 服务器输出:num:1024num:2048 → … → num:[文件总大小](与客户端一致)
  2. 传输验证
    • 传输完成后,2.png 与原始 1.png 大小完全相同(ls -l 对比)
    • 图片文件可正常打开(如 eog 2.png 无损坏)
  3. 退出机制
    • 客户端发送 ^_^ 后自动退出
    • 服务器收到 ^_^ 后自动退出
  4. 关键特性
    • 每个数据包传输后通过 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;
}

理想运行结果

  1. 初始化连接

    • 客户端发送 start 消息,服务器开始监听
    • 双方进入消息循环
  2. 聊天过程

    • 服务器终端:
      to cli:Hello  // 服务器输入
      cli:How are you?  // 显示客户端消息
      to cli:#quit  // 输入退出命令
      
    • 客户端终端:
      ser:Hello  // 显示服务器消息
      to ser:How are you?  // 客户端输入
      ser:#quit  // 收到退出命令
      
  3. 退出机制

    • 任意一方输入 #quit 后:
      • 发送方立即终止对方进程(kill
      • 本地进程退出(exit(1)
    • 双方终端同时关闭,无残留进程
  4. 关键特性

    • 通过 fork 实现双工通信:父进程处理接收,子进程处理发送
    • 退出命令 #quit 触发双向终止(避免单方退出导致僵局)
    • 消息实时显示(printf 前无缓冲,即时刷新)
http://www.xdnf.cn/news/20059.html

相关文章:

  • HTML5圣诞网站源码
  • Python基础(①①Ctypes)
  • Web安全——JWT
  • 厦门创客匠人靠谱嘛?从内容交付能力看其核心优势
  • el-tree 点击父节点无效,只能选中子节点
  • [BUUCTF-OGeek2019]babyrop详解(包含思考过程)
  • C++:类和对象(上)
  • 微软rStar2-Agent:新的GRPO-RoC算法让14B模型在复杂推理时超越了前沿大模型
  • 卷积操作原来分3种
  • 2025年工科生转型必考的十大高含金量证书!
  • 腾讯云建站多少钱?2025年最新价格曝光,0基础也能做出专业网站?实测真假
  • flutter专栏--深入剖析你的第一个flutter应用
  • 从一次Crash分析Chromium/360浏览器的悬空指针检测机制:raw_ref与BackupRefPtr揭秘
  • 留学第一天,语言不通怎么办?同声传译工具推荐来了
  • 常用假设检验方法及 Python 实现
  • 亚马逊云代理商:配置安全组规则步骤
  • kafka Partition(分区)详解
  • nestjs 阿里云服务端签名
  • 深度学习篇---SGD+Momentum优化器
  • Photoshop - Photoshop 触控手势
  • 电表连网不用跑现场!耐达讯自动化RS485转Profinet网关 远程配置+技术支持,真能做到!
  • ASP.NET 实战:用 SqlCommand 打造一个安全的用户注册功能
  • SIC8833芯片智能充气泵设计方案
  • 原创未发表!POD-PINN本征正交分解结合物理信息神经网络多变量回归预测模型,Matlab实现
  • 第二家公司虽然用PowerBI ,可能更适合用以前的QuickBI
  • pip completion工具作用(生成命令行自动补全脚本)(与pip-bash-completion区别)
  • 东土智建 | 让塔吊更聪明的“四大绝技”工地安全效率双升级
  • EasyMeeting-注册登录
  • PDF-XChange Editor:全功能PDF阅读和编辑软件
  • 《华为基本法》——企业文化的精髓,你学习了几条?