/* 程序目的: 实现从TFTP服务器下载文件,或将文件上传至TFTP服务器 */
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define ASK 0x0
#define READ 0x01
#define WRITE 0x02
#define DATA 0x0
#define DATPACK 0x03
/*用户接口函数*/
int UsrInterface(int argc,char *argv[],struct sockaddr_in seraddr,char askmessage[])
{
int length=0;
char *model = "octet"; /*数据传输模式(二进制模式)*/
char operate[10] = ""; /*存放tftp协议的操作码*/
if(argc != 4) /*必须输入6个参数: ./tftp 192.168.220.61 read(write) filename*/
{
printf("input error,please reference to example\n");
printf("form:\"./tftp 192.168.220.xxx read(write) filename blksize xxx\"\n");
exit(1);
}
if(argv[1]) /*第一个参数是服务器的IP地址*/
{
seraddr.sin_addr.s_addr = inet_addr(argv[1]);
}
operate[0] = ASK;
if( strcmp("read",argv[2]) == 0) //若第二个参数是read,表示从服务器下载文件
operate[1] = READ;
if( strcmp("write",argv[2]) == 0) //若第二个参数是write,表示向服务器上传文件
operate[1] = WRITE;
length = sprintf(askmessage,"%c%c%s%c%s%c",operate[0],operate[1],argv[3],0,model,0); //将读写请求命令打入数组中
return length;
}
/*创建套接字(UDP协议)*/
int creatsock(void)
{
int tftpfd;
if((tftpfd=socket(AF_INET,SOCK_DGRAM, 0))<0)
{
perror("socket");
exit(1);
}
return tftpfd;
}
/*从服务器下载数据*/
void download(int fd,int tftpfd,char askmessage[],int length,struct sockaddr_in seraddr)
{
int len;
unsigned char low = 1;
unsigned char high = 0;
unsigned char donwloaddata[516] = "";
socklen_t addrlen = sizeof(seraddr);
//length表示发送请求命令的实际长度.实际命令有多长就发送多长,这里千万不能使用sizeof(askmessage)
//若发送的请求命令中不包含选项,当客户端发送请求命令后,服务器直接发送数据,而不再发送OACK.
sendto(tftpfd, askmessage, length,0,(struct sockaddr*)&seraddr, addrlen);
do
{
memset(donwloaddata,0,sizeof(donwloaddata));
//从服务器接收数据(数据包括4个字节的操作码和512个字节的目标数据,共516个字节)
len = recvfrom(tftpfd, donwloaddata, sizeof(donwloaddata), 0, (struct sockaddr*)&seraddr, &addrlen);
if( (donwloaddata[2] == high) && (donwloaddata[3] == low) )
{
printf("len=%d\n",len);
//将接收到的数据写入文件内
//"donwloaddata+4"表示从数据缓存buffer的第4个字节开始写入文件(前4个字节是操作码,不能写入文件内)
//"len-4"表示写入数据的长度,该长度应去除4个字节的操作码长度
write(fd,(donwloaddata+4),(len-4));
donwloaddata[1] = 4; //设置ACK包(数据包和应答包的操作码区别在于,第1个字节不同)
//发送ACK包.缓存donwloaddata的前4个字节是ACK包,后面是从服务器上接收到的实际数据,因此只发送前4个字节即可
sendto(tftpfd, donwloaddata, 4,0,(struct sockaddr*)&seraddr, addrlen);
low++; /*数据包发送成功,则包序号加1*/
if(low == 0) /*当低字节加到FF时,高字节加1*/
high++;
}
}
while( (len == 516) || (donwloaddata[1] == 6) ); /*判断从服务器上下载的数据是否为516个字节以及是否出错*/
printf("download success\n");
}
/*向服务器上传文件*/
void upload(int fd,int tftpfd,char askmessage[],int length,struct sockaddr_in seraddr,char *argv[])
{
int readsize;
unsigned char high = 0;
unsigned char low = 1;
unsigned char uploaddata[516] = "";
socklen_t addrlen = sizeof(seraddr);
sendto(tftpfd, askmessage, length,0,(struct sockaddr*)&seraddr, addrlen); /*向服务器发送请求上传命令*/
recvfrom(tftpfd, uploaddata, 4, 0, (struct sockaddr*)&seraddr, &addrlen); /*收到服务器的ack*/
while(uploaddata[1] == 0x04) /*判断是否为服务器的ack*/
{
memset(uploaddata,0,sizeof(uploaddata));
uploaddata[0] = 0; /*操作码第一个字节*/
uploaddata[1] = 3; /*操作码第二个字节,表明是要发送数据包*/
uploaddata[2] = high; /*包序号的高字节*/
uploaddata[3] = low; /*包序号的低字节*/
readsize = read(fd,uploaddata+4,512); /*每次从文件中读取512个字节,并从缓存的第4个字节开始存储(前面4个字节是操作码)*/
sendto(tftpfd, uploaddata, readsize+4,0,(struct sockaddr*)&seraddr, addrlen); /*向服务器发送数据包*/
memset(uploaddata,0,sizeof(uploaddata));
recvfrom(tftpfd, uploaddata, 4, 0, (struct sockaddr*)&seraddr, &addrlen); /*等待服务器的ack(只接收前4个字节)*/
if( (uploaddata[2] == high) && (uploaddata[3] == low) ) /*若发送的数据包序号等于服务器返回的包序号,表明服务器正常接收数据*/
{
low++; /*服务器正常接收数据,则包序号加1*/
if(low == 0) /*当低字节加到FF时,高字节加1*/
high++;
if(readsize < 512) /*当读取到文件的小于512时,退出.此时文件的最后一包数据已发送,服务器的ack已收到*/
break;
}
else /*若发送的数据包序号不等于服务器返回的包序号,则这一包数据重发*/
continue;
}
printf("upload success\n");
}
int main(int argc,char *argv[])
{
int tftpfd;
int fd;
int flag;
int length = 0;
char askmessage[100] = "";
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(69); //TFTP服务器默认端口为69
seraddr.sin_addr.s_addr = inet_addr("192.168.220.61"); //服务器IP地址
tftpfd = creatsock();
length = UsrInterface(argc,argv,seraddr,askmessage);
if( strcmp("read",argv[2]) == 0)
{
if((fd=open(argv[3],O_WRONLY|O_CREAT,0666)) < 0)
perror("read open");
flag = 1;
}
if( strcmp("write",argv[2]) == 0)
{
if((fd=open(argv[3],O_RDONLY)) < 0)
perror("write open");
flag = 2;
}
switch(flag)
{
case 1:
download(fd,tftpfd,askmessage,length,seraddr);
break;
case 2:
upload(fd,tftpfd,askmessage,length,seraddr,argv);
break;
default:
printf("input error\n");
exit(1);
break;
}
return 0;
}