TFTP协议又称简单文件传输协议,适用于在网络上进行文件传输的一套标准协议,使用UDP传输
特点:
1、是应用层协议
2、基于UDP协议实现
数据传输模式 :octet:二进制模式
下载模型:
TFTP通信过程总结
-
服务器在69号端口等待客户端的请求
-
服务器若批准此请求,则使用 临时端口 与客户端进行通信。
-
每个数据包的编号都有变化(从1开始)
-
每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
-
数据长度以512Byte传输的,小于512Byte的数据意味着数据传输结束。
代码实现使用TFTP协议从服务器下载
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#include<netinet/in.h>
#include<pthread.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
//打印错误信息的宏函数
#define ERR_MSG(msg) do{\
fprintf(stderr,"__%d__",__LINE__);\
perror(msg);\
}while(0)
int main(int argc, const char *argv[])
{
if(argc < 3)
{
fprintf(stderr,"请输入IP号和要下载的文件名\n");
return -1;
}
//创建报式套接字
int sfd = socket(AF_INET,SOCK_DGRAM,0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("create socket success\n");
//在本地创建一个同名文件用于存放下载的数据
int fd_w=open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0664);
if(fd_w<0)
{
ERR_MSG("open");
return -1;
}
//填充地址信息结构体,真实的地址信息结构体与协议族相关
//AF_INET,详情请看man 7 ip
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(69); //网络字节序的端口号
sin.sin_addr.s_addr = inet_addr(argv[1]); //网络字节序的IP地址
socklen_t addrlen = sizeof(sin);
//发送读取请求
char buf[666]="";
char*ptr = buf;
short int* pa = (short int*)ptr;
*pa = htons(1); //操作码
char* pb = ptr+2; //偏移
strcpy(pb,argv[2]); //文件名
char*pc = pb+strlen(pb); //偏移
*pc=0;
char*pd = pc+1;
strcpy(pd,"octet"); //模式
size_t size = 2+strlen(pb)+1+strlen("octet")+1; //计算数据包长度
if(sendto(sfd,buf,size,0,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("sendto");
return -1;
}
char str[516]=""; //存储接收的数据包
char ack[4]=""; //存放ACK包
int res;
while(1)
{
bzero(str,sizeof(str));
//接收数据并写入本地
res=recvfrom(sfd,str,sizeof(str),0,(struct sockaddr*)&sin,&addrlen); //res用于判断读取数据的大小便于跳出循环
if(res<0)
{
ERR_MSG("recvfrom");
return -1;
}
char *p=str;
char *q=p+4; //偏移至数据的开头
if(res<0)
{
ERR_MSG("sprintf");
}
write(fd_w,q,res-4); //将数据写入本地
//创建ACK包
bzero(ack,sizeof(ack));
char *qa=ack;
short int *qb=(short int*)qa;
*qb=htons(4); //设定操作码
short int *qc=(short int*)(p+2); //偏移至块编号处
short int *qd=(short int*)(qa+2); //偏移至操作码后
*qd=*qc; //将读出的块编号写入ACK包中
//发送ACK包
if(sendto(sfd,ack,sizeof(ack),0,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("sendto");
return -1;
}
//判断文件是否读取完毕
if(res<516)
{
printf("文件录入完成\n");
break;
}
}
//关闭文件
close(sfd);
close(fd_w);
return 0;
}