1. tftp协议概述
简单文件传输协议,适用于在网络上进行文件传输的一套标准协议,使用UDP传输
特点:
是应用层协议
基于UDP协议实现
数据传输模式
octet:二进制模式(常用)
mail:已经不再支持
2. tftp下载模型
3. TFTP通信过程总结
- 服务器在69号端口等待客户端的请求
- 服务器若批准此请求,则使用 临时端口 与客户端进行通信。
- 每个数据包的编号都有变化(从1开始)
- 每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
- 数据长度以512Byte传输的,小于512Byte的数据意味着数据传输结束。
4. tftp协议分析
5.代码
#include<myhead.h>
#define PORT 69
#define SER_IP "192.168.10.10"
int do_download(int sfd,struct sockaddr_in sin);
int do_upload(int sfd,struct sockaddr_in sin);
// 主函数
int main(int argc, char const *argv[])
{// 创建套接字int sfd = socket(AF_INET, SOCK_DGRAM, 0);if (sfd == -1){perror("socket error");return -1;}// 初始化服务器地址结构struct sockaddr_in sin;socklen_t addrlen = sizeof(sin);sin.sin_family = AF_INET; sin.sin_port = htons(PORT); sin.sin_addr.s_addr = inet_addr(SER_IP); int choose = 0;// 主循环while (1){// 打印操作菜单printf("------------------------\n");printf("---------1. 下载--------\n");printf("---------2. 上传--------\n");printf("---------3. 退出--------\n");printf("------------------------\n");printf("------------------------\n");printf("请输入>>> ");// 获取用户选择choose = getchar();while (getchar() != 10); // 清空输入缓冲区直到遇到换行符// 根据用户选择执行相应操作switch (choose){case '1':// 执行下载操作do_download(sfd, sin);break;case '2':// 执行上传操作do_upload(sfd, sin);break;case '3':// 退出程序goto END;break;}}
END:// 关闭套接字文件描述符close(sfd);return 0;
}int do_download(int cfd, struct sockaddr_in sin)
{// 初始化char buf[516]="";char name[32]="";// 提示用户输入要下载的文件名printf("请输入要下载的文件名:");scanf("%s",name);// 设置操作码为下载操作unsigned short *p1 = (short *)buf;*p1 = htons(1);// 设置文件名char *p2 = buf+2;stpcpy(p2,name);// 设置文件名结束标志char *p3 = p2+strlen(p2);*p3 = 0;// 设置下载模式为二进制char *p4 = p3+1;strcpy(p4,"octet");// 计算发送数据的大小size_t size=2+strlen(p2)+1+strlen(p4)+1;// 发送下载请求给服务器if(sendto(cfd,buf,sizeof(buf),0,(struct sockaddr *)&sin,sizeof(sin)) == -1){perror("send error");return -1;}// 初始化int fd = -1;socklen_t lent = sizeof(sin);size_t res =0;unsigned short num =0;// 循环接收数据直到文件下载完成while (1){bzero(buf,sizeof(buf));res = recvfrom(cfd,buf,sizeof(buf),0,(struct sockaddr *)&sin,&lent);if(res < 0){perror("recv error");return -1;}// 检查数据包类型,如果是数据包则处理if(buf[1] == 3){// 检查数据包序列号是否正确if(*(unsigned short *)(buf+2) == htons(num+1)){num++;// 如果文件描述符未初始化,则打开文件if(fd == -1){fd = open(name,O_WRONLY|O_CREAT|O_TRUNC,0664);if(fd <0){perror("fd error");return -1;}}// 将接收到的数据写入文件if(write(fd,buf+4,res-4) <0){perror("write error");return -1;}// 发送ACK确认数据包已接收buf[1] = 4;if(sendto(cfd,buf,4,0,(struct sockaddr *)&sin,sizeof(sin)) == -1){perror("send error");return -1;}// 如果接收到的数据包大小小于缓冲区大小,表示文件传输完成if(res < 516){printf("文件下载完毕\n");break;}}}}// 关闭文件描述符close(cfd);return 0;
}int do_upload(int cfd, struct sockaddr_in sin)
{// 定义buf数组用于构造上传数据包char buf[516] = "";// 定义name数组用于存储上传文件名char name[32] = "";printf("请输入你要上传的文件名:");scanf("%s", name);// 打开文件,只读方式int fd = open(name, O_RDONLY);if (fd < 0){perror("error open");return -1;}// 构造上传数据包,前两个字节为操作码,此处为上传操作unsigned short *p1 = (short *)buf;*p1 = htons(2);// 构造上传数据包,接下来为文件名长度char *p2 = buf + 2;strcpy(p2, name);// 文件名后添加字符串结束符char *p3 = p2 + strlen(p2);*p3 = 0;// 添加数据类型标识,此处为二进制文件char *p4 = p3 + 1;strcpy(p4, "octet");// 发送上传数据包给服务器if (sendto(cfd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, sizeof(sin)) == -1){perror("error send");return -1;}// 定义接收服务器响应数据的变量ssize_t res = 0;unsigned short num = 0;socklen_t lent = sizeof(sin);// 循环接收服务器响应while (1){// 清空接收缓冲区bzero(buf, sizeof(buf));// 接收服务器响应res = recvfrom(cfd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, &lent);// 检查接收是否成功if (res < 0){perror("error recv");return -1;}// 检查服务器响应的操作码是否为数据包if (4 == buf[1]){// 检查服务器返回的序号是否正确if (*(unsigned short *)(buf + 2) == htons(num)){// 修改操作码为发送数据buf[1] = 3;num++;*(unsigned short *)(buf + 2) = htons(num);// 读取文件数据到buf中res = read(fd, buf + 4, sizeof(buf) - 4);// 检查读取是否成功if (res < 0){perror("read error");return -1;}else if (0 == res){// 若读取到文件末尾,打印上传成功信息并退出循环printf("上传成功\n");break;}// 发送数据包给服务器if (sendto(cfd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, sizeof(sin)) == -1){perror("send error");return -1;}}}}// 关闭文件close(fd);
}