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

2025.8.30项目二基于UDP的TFTP文件传输

        今天我们来做一下这个项目,关于一个简单文件传输协议,适用于在网络上进行文件传输的一套标准协议,使用UDP传输,与tftp32服务器互传文件,

TFTP通信过程总结
  1. 服务器在69号端口等待客户端的请求
  2. 服务器若批准此请求,则使用 临时端口 与客户端进行通信。
  3. 每个数据包的编号都有变化(从1开始)
  4. 每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
  5. 数据长度以512Byte传输的,小于512Byte的数据意味着数据传输结束。
3)tftp协议分析
差错码
0 未定义,差错错误信息
1 File not found.
2 Access violation.
3 Disk full or allocation exceeded.
4 illegal TFTP operation.
5 Unknown transfer ID.
6 File already exists.
7 No such user.
8 Unsupported option(s) requested.

        上传文件的流程

        下载流程

        标准数据包格式
所有传输的数据包严格遵循 TFTP 格式:
请求包(RRQ/WRQ):0x00 + 操作码(1/2) + 文件名 + 0x00 + 传输模式("octet") + 0x00;
数据包(DATA):0x00 + 操作码(3) + 块编号(2字节) + 数据(≤512字节);
确认包(ACK):0x00 + 操作码(4) + 块编号(2字节);
错误包(ERROR):0x00 + 操作码(5) + 错误码(2字节) + 错误信息 + 0x00;

        依旧老规矩,将我的代码奉上

#include <myhead.h>// 常量与TFTP协议操作码定义
#define IP "192.168.0.70"    // TFTP服务器IP
#define PORT 69              // TFTP默认端口(接收初始请求)
#define N 516                // 最大数据包大小(2字节操作码+2字节块号+512字节数据)
// TFTP操作码(标识包类型,协议规定)
#define OP_RRQ   1           // 读请求(下载)
#define OP_WRQ   2           // 写请求(上传)
#define OP_DATA  3           // 数据块包
#define OP_ACK   4           // 确认包
#define OP_ERROR 5           // 错误包// 上传文件:客户端→服务器
// 参数:oldfd(UDP套接字)、server(服务器地址)
int do_upload(int oldfd, struct sockaddr_in server)
{char filename[20]; printf("请输入上传文件名: ");fgets(filename, 20, stdin);filename[strlen(filename)-1] = 0;  // 去除换行符// 检查本地文件是否存在(只读打开)int fd = open(filename, O_RDONLY);if(fd == -1) { perror("open"); printf("打开文件失败!\n"); return -1; }// 构造并发送WRQ(写请求)包char buf[N];// WRQ格式:0x00+OP_WRQ+文件名+0x00+"octet"+0x00(octet=二进制传输)int size = sprintf(buf, "%c%c%s%c%s%c", 0, OP_WRQ, filename, 0, "octet", 0);if(sendto(oldfd, buf, size, 0, (struct sockaddr*)&server, sizeof(server)) < 0){ perror("sendto"); close(fd); return -1; }// 循环传数据:收ACK→发数据块int recv_len;unsigned short num = 1;  // 数据块编号(从1开始)socklen_t addrlen = sizeof(server);while(1){bzero(buf, N);recv_len = recvfrom(oldfd, buf, N, 0, (struct sockaddr*)&server, &addrlen);if(recv_len == -1) { perror("recvfrom"); close(fd); return -1; }if(OP_ACK == buf[1])  // 处理服务器ACK{// 提取ACK块号(网络字节序转主机字节序)unsigned short ack_num = ntohs(*(unsigned short*)(buf+2));// 校验ACK:首次需ACK=0,后续需ACK=num-1if((num == 1 && ack_num == 0) || (ack_num == num - 1)){// 构造DATA包(0x00+OP_DATA+块号+数据)buf[0] = 0; buf[1] = OP_DATA;*(unsigned short*)(buf+2) = htons(num);  // 块号转网络字节序// 读本地文件数据(跳过4字节头部,最多读512字节)int res = read(fd, buf+4, N-4);if(res == -1) { perror("read"); close(fd); return -1; }if(res == 0) { printf("上传完毕\n"); break; }  // 数据读完,结束// 发送DATA包if(sendto(oldfd, buf, res+4, 0, (struct sockaddr*)&server, sizeof(server))==-1){ perror("sendto"); close(fd); return -1; }num++;  // 块号自增} else { printf("编号不匹配: 期望%d, 实际%d\n", num-1, ack_num); break; }}else if(OP_ERROR == buf[1])  // 处理服务器错误{ printf("错误: %s\n", buf+4); break; }}close(fd);return 0;
}// 下载文件:服务器→客户端
// 参数:oldfd(UDP套接字)、server(服务器地址)
int do_download(int oldfd, struct sockaddr_in server)
{char filename[20];printf("请输入下载文件名: ");fgets(filename, 20, stdin);filename[strlen(filename)-1] = 0;  // 去除换行符// 构造并发送RRQ(读请求)包char buf[N];// RRQ格式:0x00+OP_RRQ+文件名+0x00+"octet"+0x00int size = sprintf(buf, "%c%c%s%c%s%c", 0, OP_RRQ, filename, 0, "octet", 0);if(sendto(oldfd, buf, size, 0, (struct sockaddr*)&server, sizeof(server))==-1){ perror("sendto"); return -1; }// 循环收数据:收DATA→写文件→发ACKint fd, flag = 0;  // flag=1表示文件已创建ssize_t recv_len;unsigned short num = 1;  // 期望接收的块号socklen_t addrlen = sizeof(server);while(1){bzero(buf, N);recv_len = recvfrom(oldfd, buf, N, 0, (struct sockaddr*)&server, &addrlen);if(recv_len == -1) { perror("recvfrom"); return -1; }if(OP_DATA == buf[1])  // 处理服务器DATA包{if(!flag)  // 首次收数据,创建本地文件{fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0664);if(fd == -1) { perror("open"); return -1; }flag = 1;}// 提取DATA块号,校验是否连续unsigned short current_num = ntohs(*(unsigned short*)(buf+2));if(current_num == num){// 写数据到本地文件(跳过4字节头部)if(write(fd, buf+4, recv_len-4)==-1) { perror("write"); close(fd); return -1; }// 构造并发送ACK(确认已收当前块)buf[0] = 0; buf[1] = OP_ACK;if(sendto(oldfd, buf, 4, 0, (struct sockaddr*)&server, sizeof(server))==-1){ perror("sendto"); }// 数据<512字节(包长<516),表示传输结束if(recv_len < 516) { printf("下载完毕\n"); break; }num++;  // 块号自增}}else if(OP_ERROR == buf[1])  // 处理服务器错误{ printf("错误: %s\n", buf+4); break; }}if(flag) close(fd);  // 关闭本地文件return 0;
}// 主函数:初始化套接字+用户交互
int main(int argc, const char *argv[])
{// 创建UDP套接字int oldfd = socket(AF_INET, SOCK_DGRAM, 0);if(oldfd == -1) { perror("socket失败"); return -1; }// 初始化服务器地址struct sockaddr_in server = {.sin_family = AF_INET,.sin_port = htons(PORT),  // 端口转网络字节序.sin_addr.s_addr = inet_addr(IP)  // IP转网络字节序};// 用户交互菜单char choose;while(1){printf("******************\n");printf("******1.下载******\n");printf("******2.上传******\n");printf("******3.退出******\n");printf("******************\n");printf("请选择你的操作:\n");choose = getchar();while(getchar()!='\n');  // 清空输入缓冲区switch(choose){case '1': do_download(oldfd, server); break;case '2': do_upload(oldfd, server); break;case '3': goto END;  // 退出循环default: printf("输入错误\n");}}END:close(oldfd);  // 关闭套接字return 0;
}

http://www.dtcms.com/a/359277.html

相关文章:

  • 【ICO】快速制作ICON教材/使用icofx3快速制作ico
  • 【多项式】快速沃尔什变换 (FWT)
  • 复现 RoboDK 机器人校准功能(以Staubli TX2‑90L / TX200机械臂为测试对象)
  • 关于铭飞平台企业官网模板使用中常到的问题、企业官网的百度认证以及IDEA编辑启动器的快捷方法/Apipost本地和云端没法同步的问题解决
  • 如何改变传统教育的消费习惯-第三代结束-第四代开启
  • 数值分析——数据误差对函数值的影响
  • 数据治理进阶——26页如何进行数据治理【附全文阅读】
  • 项目管理方法论有哪些流派
  • TuringComplete游戏攻略(一、基础逻辑电路)
  • Python(五)Python_C API详细
  • 嵌入式Linux输入子系统驱动开发
  • [光学原理与应用-332]:ZEMAX - 序列模式与非序列模式的本质、比较
  • FPGA 实现FOC 无刷电机控制器
  • 电子健康记录风险评分与多基因风险评分的互补性与跨系统推广性研究
  • 洛谷 P1395 会议 -普及/提高-
  • 吴恩达机器学习(四)
  • 10. 函数和匿名函数(二)
  • 深入理解 shared_ptr 与 weak_ptr:访问控制与线程安全
  • 广东省省考备考(第九十天8.30)——判断推理(第十节课)
  • Java多线程初阶
  • C++讲解---如何设计一个类
  • 防火墙技术(三):状态检测和会话机制
  • 接口自动化测试框架
  • python pyqt5开发DoIP上位机【自动化测试的逻辑是怎么实现的?】
  • 深度解析Fluss LockUtils类的并发艺术
  • 手写MyBatis第43弹:插件拦截原理与四大可拦截对象详解
  • Agent实战教程:LangGraph结构化输出详解,让智能体返回格式化数据
  • Keil5 MDK_541官网最新版下载、安装
  • offsetof宏的实现
  • 线程池项目代码细节2