代码实现:
//tftp客户端
#include <head.h>
#define ERR_LOG(msg) do{\
perror(msg);\
printf("%d %s %s\n",__LINE__,__func__,__FILE__);\
}while(0)
#define PORT 69
//下载功能函数
int do_download(int sfd,struct sockaddr_in ser)
{
char filename[20]="";
printf("请输入要下载的文件名:");
fgets(filename,20,stdin);
filename[strlen(filename)-1]='\0';
//发送下载请求
char buf[516]="";
int size=sprintf(buf,"%c%c%s%c%s%c",0,1,filename,0,"octet",0);
sendto(sfd,buf,size,0,(struct sockaddr*)&ser,sizeof(ser));
int flag=0;
int fd;
//循环接收发送应答包
int recv_len;
unsigned short num=1;
socklen_t addrlen=sizeof(ser);
while(1)
{
bzero(buf,516);
recv_len=recvfrom(sfd,buf,516,0,(struct sockaddr*)&ser,&addrlen);
if(recv_len < 0)
{
ERR_LOG("recvfrom");
return -1;
}
if(buf[1]==3) //如果是数据包
{
if(flag==0) //防止文件重复打开
{
fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0664);
flag=1;
}
//判断当前的快编号,是否与期望的快编号一致
if( htons(num) == *(unsigned short*)(buf+2) ) //防止数据包重复到达
{
if(write(fd,buf+4,recv_len-4) < 0)
{
printf("fd:%d recv_len=%ld\n",fd,recv_len);
ERR_LOG("write");
break;
}
//回复ack包
//由于数据包前四个字节与ack包除了操作码不一样,其余全部一致
//发送数据包的前四个字节
buf[1]=4;
sendto(sfd,buf,4,0,(struct sockaddr*)&ser,sizeof(ser));
//判断数据包的大小是否小于 512+2+2
if(recv_len < 512+2+2)
{
printf("-----文件下载完成!-----\n");
break;
}
num++;
}
}
else if(buf[1]==5) //错误包
{
//打印错误信息
printf("---ERROR:%s---\n",buf+4);
break;
}
}
return 0;
}
//上传功能函数
int do_upload(int sfd,struct sockaddr_in ser)
{
char filename[20]="";
printf("请输入要上传的文件名:");
fgets(filename,20,stdin);
filename[strlen(filename)-1]='\0';
//判断该文件是否存在
int fd=open(filename,O_RDONLY);
if(fd < 0)
{
if(errno==ENOENT)
{
printf(">>>文件不存在,请重新输入<<<\n");
return -2;
}
else
{
ERR_LOG("open");
return -1;
}
}
//发送上传请求
//上传协议
char buf[516]="";
int size=sprintf(buf,"%c%c%s%c%s%c",0,2,filename,0,"octet",0);
sendto(sfd,buf,size,0,(struct sockaddr*)&ser,sizeof(ser));
//循环接收发送数据包
int recv_len;
unsigned short num=0;
socklen_t addrlen=sizeof(ser);
while(1)
{
bzero(buf,516);
recv_len=recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&ser,&addrlen);
if(recv_len < 0)
{
ERR_LOG("recvfrom");
return -1;
}
//操作码的范围是1-5,因为是网络字节序
//所以有效操作码存储在高位,即buf[1]的位置
if(buf[1]==4)
{
//判断当前数据包的编号是否等于应答包的编号
//防止数据包在传送过程中丢包或者重复收包
if( num==ntohs(*(unsigned short*)(buf+2)) )
{
//修改操作码为数据包
buf[1]=3;
//填充块编号
num++;
*(unsigned short*)(buf+2)=htons(num);
//读取数据
//发送数据
int res=read(fd,buf+4,516-4);
if(res < 0)
{
ERR_LOG("read");
return -1;
}
else if(res==0)
{
printf("-----文件上传完毕-----\n");
break;
}
//发送数据包
sendto(sfd,buf,res+4,0,(struct sockaddr*)&ser,sizeof(ser));
}
else
{
printf("-----文件上传失败,请检查网络环境-----\n");
break;
}
}
else if(buf[1]=5)
{
printf("---ERROR:%s---\n",buf+4);
break;
}
}
return 0;
}
int main(int argc, const char *argv[])
{
if(argc != 2)
{
printf("input error\n");
printf("usage:./a.out ip\n");
return -1;
}
int sfd=socket(AF_INET,SOCK_DGRAM,0);
//填充服务器地址信息结构体
struct sockaddr_in ser;
ser.sin_family=AF_INET;
ser.sin_port=htons(PORT);
ser.sin_addr.s_addr=inet_addr(argv[1]);
socklen_t socklen=sizeof(ser);
int menu=-1;
while(1)
{
system("clear");
printf("\t=====1、下载=====\n");
printf("\t=====2、上传=====\n");
printf("\t=====0、退出=====\n");
printf("请输入功能选项:");
scanf("%d",&menu);
getchar(); //吸收回车
switch(menu)
{
case 1:
{
do_download(sfd,ser);
}
break;
case 2:
{
do_upload(sfd,ser);
}
break;
case 0:goto END;
default:printf("input error,try again!\n");
}
//阻塞
printf("输入任意键,按回车清空:");
while(getchar() != '\n');
}
END:
close(sfd);
return 0;
}