tftp下载模型
TFTP通信过程总结
- 服务器在69号端口等待客户端的请求
- 服务器若批准此请求,则使用 临时端口 与客户端进行通信。
- 每个数据包的编号都有变化(从1开始)
- 每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
- 数据长度以512Byte传输的,小于512Byte的数据意味着数据传输结束。
tftp协议分析
上传流程
客户端会向TFTP服务器发送请求写入(WRQ)数据包,指明要写入的文件。如果TFTP服务器允许该文件的写入,就返回一个ACK确认包,该包的编号为0。客户端收到服务器的确认包以后,就开始向服务器写入文件。文件数据以定长512字节进行传输,与下载的传输方式一样,传输的每一个文件数据包都会得到服务器返回的确认包,并且数据包的数据编号也是从1开始。
客户端代码示例
#include <stdio.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#define ERR_MSG(msg) do{\
fprintf(stderr, " __%d__ ", __LINE__);\
perror(msg);\
}while(0)
#define IP "192.168.31.13"
int do_download(int sfd,struct sockaddr_in sin)
{
//发送请求
char str[777] = "";
char *ptr = str;
short int *ptr1 = (short int*)ptr;
*ptr1 = htons(1);
char *ptr2 = ptr+2;
char name[128] = "";
printf("请输入要下载的文件名>>>");
scanf("%s",name);
getchar();
strcpy(ptr2,name);
char *ptr3 = ptr2+strlen(ptr2)+1;
strcpy(ptr3,"octet");
int size = 2+strlen(ptr2)+1+strlen(ptr3)+1;
if(sendto(sfd, str, size, 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("sendto success\n");
struct sockaddr_in rcv_addrmsg; //存储接收到的数据包来自哪里
socklen_t addrlen = sizeof(rcv_addrmsg);
//打开文件
int fd = open(name,O_WRONLY|O_TRUNC|O_CREAT,0777);
if(fd<0)
{
ERR_MSG("open");
return -1;
}
short int num = 1; //块编号
char str2[1024] = ""; //数据包
char str3[6] = ""; //ACK
ssize_t res = 0;
while(1)
{
//接收数据包
res = recvfrom(sfd,str2,sizeof(str2),0,(struct sockaddr*)&rcv_addrmsg,&addrlen);
if(res < 0)
{
ERR_MSG("recvfrom");
return -1;
}
//判断操作码是否为5,为5是错误报
char *sjb = str2;
short int *err;
err = (short int*)sjb;
if(ntohs(*err) == 5)
{
printf("error:%s\n",(char*)(err+2));
return -1;
}
//操作码不为5,即为3,即是数据包
write(fd,sjb+4,res-4);
short int *kbh = (short int*)str2;
if(ntohs(*(kbh+1)) != num)
{
continue;
}
num++;
char *ack = str3;
short int *pa1 = (short int*)ack;
*pa1 = htons(4);
*(pa1+1) = *(kbh+1);
//发送ACK
if(sendto(sfd, str3,4,0,(struct sockaddr*)&rcv_addrmsg,addrlen) < 0)
{
ERR_MSG("sendto");
return -1;
}
if(res < 516)
{
break;
}
}
printf("下载完成\n");
return 0;
}
int do_upload(int sfd,struct sockaddr_in sin)
{
//发送写入请求
char str[777] = "";
char *ptr = str;
short int *ptr1 = (short int*)ptr;
*ptr1 = htons(2);
char *ptr2 = ptr+2;
char name[128] = "";
printf("请输入要上传的文件名>>>");
scanf("%s",name);
getchar();
strcpy(ptr2,name);
char *ptr3 = ptr2+strlen(ptr2)+1;
strcpy(ptr3,"octet");
int size = 2+strlen(ptr2)+1+strlen(ptr3)+1;
if(sendto(sfd, str, size, 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("sendto success\n");
char str1[6] = ""; //接收ACK
struct sockaddr_in rcv_addrmsg; //存储接收到的ACK来自哪里
socklen_t addrlen = sizeof(rcv_addrmsg);
//接收ACK确认
if(recvfrom(sfd,str1,sizeof(str1),0,(struct sockaddr*)&rcv_addrmsg,&addrlen) < 0)
{
ERR_MSG("recvfrom");
return -1;
}
//打开文件
int fd = open(name,O_RDWR);
if(fd<0)
{
ERR_MSG("open");
return -1;
}
//封装数据包
short int num = 1; //数据包块编号
char str2[1024] = "";
//读取文件数据并存入数据包中
ssize_t rts = 0;
while(1)
{
//封装数据包
char *sjb = str2;
short int *sjb1 = (short int*)sjb;
*sjb1 = htons(3);
short int *sjb2 = (short int*)(sjb+2);
*sjb2 = htons(num);
char *sjb3 = (str2+4);
rts = read(fd,sjb3,512);
printf("%ld\n",rts);
int sjblen = rts+4;
//发送数据包
if(sendto(sfd,str2,sjblen,0,(struct sockaddr*)&rcv_addrmsg,addrlen) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("sendto success\n");
//接收ACK
bzero(str1,sizeof(str1));
if(recvfrom(sfd,str1,sizeof(str1),0,(struct sockaddr*)&rcv_addrmsg,&addrlen) < 0)
{
ERR_MSG("recvfrom");
return -1;
}
//判断ACK操作码是否为5,为5是错误报
char *ack = str1;
short int *err;
err = (short int*)ack;
if(ntohs(*err) == 5)
{
printf("error:%s\n",(char*)(err+2));
return -1;
}
//操作码不为5,即为4,即是ACK,判断块编号
short int *kbh = (short int*)str1;
if(ntohs(*(kbh+1)) != num)
{
continue;
}
num++;
if(rts != 512)
{
break;
}
}
}
int main(int argc, const char *argv[])
{
//创建报式套接字
int sfd = socket(AF_INET,SOCK_DGRAM,0);
if(sfd < 0)
{
perror("socket");
return -1;
}
//填充服务器的地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(69);
sin.sin_addr.s_addr = inet_addr(IP);
int a = 0;
while(1)
{
printf("**************\n");
printf("** 1.下载 **\n");
printf("** 2.上传 **\n");
printf("** 3.退出 **\n");
printf("请选择你的数字!>>>\n");
scanf("%d",&a);
switch(a)
{
case 1:
do_download(sfd,sin);
break;
case 2:
do_upload(sfd,sin);
break;
case 3:
goto END;
default:
printf("你是猪??????\n");
}
}
END:
close(sfd);
return 0;
}