tftp协议概述
简单文件传输协议,适用于在网络上进行文件传输的一套标准协议,使用UDP传输
特点:
是应用层协议
基于UDP协议实现
数据传输模式
octet:二进制模式(常用)
mail:已经不再支持
TFTP通信过程总结
- 服务器在69号端口等待客户端的请求
- 服务器若批准此请求,则使用 临时端口 与客户端进行通信。
- 每个数据包的编号都有变化(从1开始)
- 每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
- 数据长度以512Byte传输的,小于512Byte的数据意味着数据传输结束
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.
#include "../header.h" #define S_IP "192.168.125.30" #define S_PORT 69 typedef struct file_header { int size; char *file_report; } file_header_report; char buf[516] = ""; char fileName[128] = {0}; int sockfd = -1; int data_num = 0; void download_file(struct sockaddr_in *addr); void upload_file(struct sockaddr_in *addr); void dealerror(short errno); file_header_report get_report_RW(short type); file_header_report get_report_ACK(short type, short blknumber); int main(int argc, char const *argv[]) { // 服务器地址 ,初始地址用于链接请求,但是不是用于实际数据交互 struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(S_PORT); addr.sin_addr.s_addr = inet_addr(S_IP); char choose = 0; while (1) { printf("**************************\n"); printf("******** 1. 下载 *********\n"); printf("******** 2. 上传 *********\n"); printf("******** 3. 退出 *********\n"); printf("**************************\n"); printf("请输入>>> "); choose = getchar(); while (getchar() != 10) { }; switch (choose) { case '1': download_file(&addr); break; case '2': upload_file(&addr); break; case '3': exit(EXIT_SUCCESS); break; default: printf("输入错误!请重新输入\n"); } } close(sockfd); return 0; } void upload_file(struct sockaddr_in *addr) { if (sockfd == -1) { // 判断一下,防止重复创建套接字 sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("socket"); return; } } // 发送上传请求 printf("请输入要上传的文件名:\n"); file_header_report file_report = get_report_RW(2); if (access(fileName, F_OK) != 0) { printf("文件不存在\n"); return; } data_num = 0; if (sendto(sockfd, file_report.file_report, file_report.size, 0, (struct sockaddr *)addr, sizeof(struct sockaddr)) == -1) { perror("1111sendto 数据包"); return; } // recv_addr 用于 实际数据交互的地址 struct sockaddr_in recv_addr; socklen_t recv_addr_len = sizeof(recv_addr); int read_size = recvfrom(sockfd, buf, 516, 0, (struct sockaddr *)&recv_addr, &recv_addr_len); short *first_no = (short *)buf; short *blk_num = (short *)(buf + 2); // 处理报错信息 == 5 if (ntohs(*first_no) == 5) { dealerror(ntohs(*blk_num)); return; } if (ntohs(*first_no) == 4) { int upload_fd = open(fileName, O_RDONLY); if (upload_fd < 0) { perror("open"); return; } while (1) { // 发送数据包 bzero(buf, sizeof(buf)); // 拼接数据包 short *p1 = (short *)buf; *p1 = htons(3); short *p2 = (short *)(buf + 2); *p2 = htons(data_num); int file_size = read(upload_fd, buf + 4, 512); if (file_size < 0) { perror("read"); exit(1); } if (sendto(sockfd, buf, file_size + 4, 0, (struct sockaddr *)&recv_addr, sizeof(recv_addr)) == -1) { perror("sendto 数据包"); exit(1); } bzero(buf, sizeof(buf)); int read_size_n = recvfrom(sockfd, buf, 4, 0, (struct sockaddr *)&recv_addr, &recv_addr_len); short *first_noe_n = (short *)buf; short *blk_nume_n = (short *)(buf + 2); // 确认ACK包 // 编号是否与本地的标号一致,一致的话就进行下一个编号的数据传送 if (ntohs(*first_noe_n) == 4 && ntohs(*blk_nume_n) == data_num) { // 这种情况说明本次上传成功 data_num++; } else { printf("上传失败\n"); if (ntohs(*first_no) == 5) { dealerror(ntohs(*blk_num)); return; } break; } // 如果最后一次读取,就结束循环 if (file_size < 512) { printf("上传成功\n"); break; } } } else { printf("上传失败\n"); } } // 下载 void download_file(struct sockaddr_in *addr) { if (sockfd == -1) { // 判断一下,防止重复创建套接字 sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("socket"); return; } } // 发送下载请求 printf("请输入要下载的文件名:\n"); file_header_report file_report = get_report_RW(1); if (sendto(sockfd, file_report.file_report, file_report.size, 0, (struct sockaddr *)addr, sizeof(struct sockaddr)) == -1) { perror("sendto 数据包"); return; } struct sockaddr_in recv_addr; socklen_t recv_addr_len = sizeof(recv_addr); while (1) { // 接收数据包 // 数据包返回的服务器端地址需要存在recv_addr,因为服务器处理数据的端口并非原来端口, // 该地址recv_addr 用于后续的ack包 发送给服务器用于确认 bzero(buf, sizeof(buf)); int read_size = recvfrom(sockfd, buf, 516, 0, (struct sockaddr *)&recv_addr, &recv_addr_len); int download_fd = open(fileName, O_RDWR | O_CREAT | O_APPEND, 0666); if (download_fd < 0) { perror("open"); return; } short *first_no = (short *)buf; short *blk_num = (short *)(buf + 2); // 处理报错信息 == 5 if (ntohs(*first_no) == 5) { dealerror(ntohs(*blk_num)); break; } // 这是数据包 == 3 if (ntohs(*first_no) == 3) { if (read_size > 4) { write(download_fd, buf + 4, read_size - 4); } // 发送ACK *first_no = htons(4); char newbuf[4] = {0}; short *p1 = (short *)newbuf; *p1 = htons(4); short *p2 = (short *)(newbuf + 2); *p2 = *blk_num; if (sendto(sockfd, newbuf, 4, 0, (struct sockaddr *)&recv_addr, sizeof(recv_addr)) == -1) { perror("sendtoACK\n"); } printf("发送ACK成功\n"); if (read_size < 516) { printf("下载完成\n"); break; } } close(download_fd); } } // 处理报错信息 void dealerror(short errno) { int erno = 0; switch (errno) { case 1: printf("File not found"); break; case 2: printf("Access violation"); break; case 3: printf("Disk full or allocation exceeded"); break; case 4: printf("illegal TFTP operation"); break; case 5: printf("Unknown transfer lD"); break; case 6: printf("File already exists"); break; case 7: printf("No such user"); break; case 8: printf("Unsupported option(s) requested"); break; default: break; } return; } /** * @description: 组装请求体数据包 * @param {short} type 1--- 下载 2---上传 * @return {*} */ file_header_report get_report_RW(short type) { bzero(buf, sizeof(buf)); fgets(fileName, sizeof(fileName), stdin); if (strlen(fileName) > 1) { fileName[strlen(fileName) - 1] = '\0'; } short *p1 = (short *)buf; *p1 = htons(type); char *p2 = buf + 2; strcpy(p2, fileName); char *p4 = p2 + strlen(p2) + 1; strcpy(p4, "octet"); int size = 2 + strlen(p2) + strlen(p4) + 2; file_header_report fh = {size, buf}; return fh; }