1)tftp协议概述
简单文件传输协议,适用于在网络上进行文件传输的一套标准协议,使用UDP传输
特点:
是应用层协议
基于UDP协议实现
数据传输模式
octet:二进制模式(常用)
mail:已经不再支持
2)tftp下载模型
TFTP通信过程总结
- 服务器在69号端口等待客户端的请求
- 服务器若批准此请求,则使用 临时端口 与客户端进行通信。
- 每个数据包的编号都有变化(从1开始)
- 每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
- 数据长度以512Byte传输的,小于512Byte的数据意味着数据传输结束。
代码实现
main.c
#include"tftp.h"
int main(int argc, const char *argv[])
{
int cmd = 0;//用来标记操作
char filename[20] = "";//存放文件名
int cfd = socket(AF_INET,SOCK_DGRAM,0);//创建套接字文件
if(cfd == -1)
{
perror("socket error");
return -1;
}
while(1)
{
menu();
printf("请输入要执行的操作:");
scanf("%d",&cmd);
switch(cmd)
{
case 1:
printf("请输入文件名:");
scanf("%s",filename);
download(cfd,filename);
break;
case 2:
printf("请输入文件名:");
scanf("%s",filename);
upload(cfd,filename);
break;
case 3:
printf("退出\n");
goto END;
break;
default:
printf("输入错误\n");
break;
}
}
END:
//关闭文件描述符
close(cfd);
return 0;
}
tftp.c
#include"tftp.h"
//菜单
void menu()
{
printf("-------------------------\n");
printf("--------1.下载文件--------\n");
printf("--------2.上传文件--------\n");
printf("--------3.退出------------\n");
printf("-------------------------\n");
}
//上传
int upload(int cfd , char *filename)
{
char buf[N] = "";
short *p1 = (short*)buf;
*p1 = htons(2);//设置操作码为2,表示上传
char *p2 = buf + 2;//文件名的起始位置
strcpy(p2,filename);
char *p4 = p2 + strlen(p2) + 1;//模式的起始位置
strcpy(p4,"octet");
int size = 2 + strlen(p2) + strlen(p4) + 2;//计算请求包的总长度
//打开本地文件
int fd = open(filename,O_RDONLY);
if(fd == -1)
{
perror("open error");
return -1;
}
//绑定服务器地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT);
sin.sin_addr.s_addr = inet_addr(SER_IP);
socklen_t len = sizeof(sin);
//给服务段发送请求
if(sendto(cfd,buf,size,0,(struct sockaddr*)&sin,sizeof(sin)) == -1)
{
perror("sendto error");
return -1;
}
short flag = 1;//设置标示位
//收发数据
while(1)
{
//清空数据
bzero(buf,sizeof(buf));
if(recvfrom(cfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,&len)==-1)
{
perror("recvfrom error");
return -1;
}
//将ack改造成为数据包
if(buf[1] == 4 || flag == buf[2])
{
int res = read(fd,buf+4,sizeof(buf)-4);//从buf的5个开始放数据,读取buf-4个数据
if(res < 0)
{
perror("read error");
return -1;
}
if(res == 0)
{
printf("文件发送完毕\n");
return -1;
}
buf[1]=3;
buf[3] = flag;
if(sendto(cfd,buf,res+4,0,(struct sockaddr*)&sin,sizeof(sin)) == -1)
{
perror("sendto error");
return -1;
}
flag++;
}
if(buf[1] == 5)
{
printf("接收错误\n");
break;
}
}
}
//下载
int download(int cfd ,char *filename)
{
char buf[N] = "";
short *p1 = (short*)buf;
*p1 = htons(1);//设置操作码为1,表示下载
char *p2 = buf + 2;//文件名的起始位置
strcpy(p2,filename);
char *p4 = p2 + strlen(p2) + 1;//模式的起始位置
strcpy(p4,"octet");
int size = 2 + strlen(p2) + strlen(p4) + 2;//计算请求包的总长度
//创建本地文件
int fd = open(filename,O_CREAT|O_WRONLY|O_TRUNC,0644);
if(fd == -1)
{
perror("open error");
return -1;
}
//绑定服务器地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT);
sin.sin_addr.s_addr = inet_addr(SER_IP);
socklen_t len = sizeof(sin);
//给服务段发送请求
if(sendto(cfd,buf,size,0,(struct sockaddr*)&sin,sizeof(sin)) == -1)
{
perror("sendto error");
return -1;
}
short flag = 1;//设置标示位
while(1)
{
//清空
bzero(buf,sizeof(buf));
//获取服务器发来的包
int res = recvfrom(cfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,&len);
if(res == -1)
{
perror("recvfrom error");
return -1;
}
//判断是否是数据包
if(buf[1] == 3 && flag == ntohs(*(short*)(buf+2)))
{
//将数据写入文件中
if(write(fd,buf+4,N-4)==-1)
{
perror("write error");
return -1;
}
//返回ack
buf[1] = 4;
if(sendto(cfd,buf,4,0,(struct sockaddr*)&sin,sizeof(sin))==-1)
{
perror("sendto error");
return -1;
}
flag++;//确保和需要的块编号一致
}
//判断是否是错误包
if(buf[1] == 5)
{
printf("文件发送错误\n");
}
//判断是否发送完毕
if(res < 516)
{
printf("文件接收完毕\n");
return -1;
}
}
}
tftp.h
#ifndef TFTP_H
#define TFTP_H
#define SER_PORT 69
#define SER_IP "192.168.1.4"
#define N 516
#include<myhead.h>
//菜单
void menu();
//上传
int upload(int cfd , char *filename);
//下载
int download(int cfd ,char *filename);
#endif
效果展示