1)tftp协议概述
简单文件传输协议,适用于在网络上进行文件传输的一套标准协议,使用UDP传输
特点:
是应用层协议
基于UDP协议实现
数据传输模式
octet:二进制模式(常用)
mail:已经不再支持
2)tftp下载模型
TFTP通信过程总结
- 服务器在69号端口等待客户端的请求
- 服务器若批准此请求,则使用 临时端口 与客户端进行通信。
- 每个数据包的编号都有变化(从1开始)
- 每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
- 数据长度以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.
#include<myhead.h>
#define SER_PORT 69 //服务器端口号
#define SER_IP "192.168.43.48" //服务器ip地址
//定义一个菜单
void menu()
{
printf("**********1.下载********\n");
printf("**********2.上传********\n");
printf("**********0.退出********\n");
}
//定义下载函数
int download_file(int cfd,struct sockaddr_in sin)
{
//编辑读写请求
char buf[516]="";
char file_name[20]="";
printf("请输入要下载的文件名>>>");
scanf("%s",file_name);
short *p1=(short *)buf;
*p1=htons(1); //操作码
char *p2=buf+2;
strcpy(p2,file_name); //文件名
char *p4=p2+strlen(p2)+1; //模式
strcpy(p4,"octet");
int size =2+strlen(p2)+strlen(p4)+2;
if(sendto(cfd,buf,size,0,(struct sockaddr*)&sin,sizeof(sin))==-1)
{
perror("发送error");
return -1;
}
socklen_t addrlen =sizeof(sin); //接受地址长度
//记录本地块编号
short num =1;
int fd=-1;
while(1)
{
//清空容器
bzero(buf,sizeof(buf));
//从套接字中读取数据
int res=recvfrom(cfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,&addrlen);
if(res==-1)
{
perror("recvfrom error");
return -1;
}
//接收服务器端发送的数据包
if(3==buf[1])
{
//判断服务器端传来的块编号和本地块编号是否一致
if(*(short*)(buf+2)==htons((num)))
{
num++;
if((fd=open(file_name,O_WRONLY|O_CREAT|O_TRUNC,0664))==-1)
{
perror("open error");
return -1;
}
}
//写入数据
write(fd,buf+4,res-4);
//给服务器发送一个ACK
buf[1]=4;
if(sendto(cfd,buf,4,0,(struct sockaddr*)&sin,sizeof(sin))==-1)
{
perror("发送error");
return -1;
}
if(res-4<512)
{
printf("%s 下载完毕\n",file_name);
close(fd);
break;
}
}
else if(5==buf[1]) //错误包
{
printf("错误:%d %s\n",ntohs(*(short*)(buf+2)),buf+4);
close(fd);
return -1;
}
}
close(fd);
return 0;
}
//定义上传函数
int upload_file(int cfd,struct sockaddr_in sin)
{
//编辑读写请求
char buf[516]="";
char file_name[20]="";
printf("请输入文件名>>>");
scanf("%s",file_name);
int rfd=-1;
if((rfd=open(file_name,O_RDONLY))==-1)
{
perror("open error");
return -1;
}
short *p1=(short *)buf;
*p1=htons(2);//操作码
char *p2=buf+2;
strcpy(p2,file_name);
char *p4=p2+strlen(p2)+1;//模式
strcpy(p4,"octet");
int size=2+strlen(p2)+strlen(p4)+2;
if(sendto(cfd,buf,size,0,(struct sockaddr*)&sin,sizeof(sin))==-1)
{
perror("发送error");
return -1;
}
//循环发送数据包
//记录本地编号
short num=0;
socklen_t addrlen=sizeof(sin);
while(1)
{
//清空容器
bzero(buf,sizeof(buf));
//将数据读取到buf中去
int res=recvfrom(cfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,&addrlen);
if(res==-1)
{
perror("recvfrom error");
return -1;
}
if(4==buf[1])
{
//判断快编号
if(num==ntohs(*(short*)(buf+2)))
{
//修改操作码为数据包
buf[1]=3;
//填充块编号
num++;
*(short*)(buf+2)=htons(num);
//读取数据
int res=read(rfd,buf+4,sizeof(buf)-4);
if(res==0)
{
printf("%s 文件上传完毕\n",file_name);
break;
}
//发送数据包
if(sendto(cfd,buf,sizeof(buf),0,(struct sockaddr *)&sin,sizeof(sin))<0)
{
perror("sendto error");
return -1;
}
}
else
{
printf("文件上传失败\n");
break;
}
}
else if(5==buf[1])
{
printf("错误:%d %s\n",ntohs(*(short*)(buf+2)),buf+4);
close(rfd);
return -1;
}
}
close(rfd);
return 0;
}
int main(int argc, const char *argv[])
{
//1、创建用于通信的服务器套接字文件描述符
int cfd = socket(AF_INET, SOCK_DGRAM, 0);
if(cfd == -1)
{
perror("socket error");
return -1;
}
printf("sfd = %d\n", cfd);
//填充地址结构体信息
struct sockaddr_in sin;
sin.sin_family=AF_INET; //通信域
sin.sin_port=htons(SER_PORT); //端口号
sin.sin_addr.s_addr=inet_addr(SER_IP); //ip地址
while(1)
{
menu();
int num=0;
printf("请输入>>>");
scanf("%d",&num);
switch(num)
{
case 1:
//调用下载函数
download_file( cfd, sin);
break;
case 2:
//调用上传函数
upload_file(cfd,sin);
break;
case 0:exit(0);
default:printf("您的输入有误请重新输入\n");
break;
}
}
close(cfd);
return 0;
}