基于UDP的tftp客户端:实现上传、下载功能。
ubuntu@ubuntu:~/internet/exercise/03_UDP$ cat 04_udptftp.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
int do_download(int cfd,struct sockaddr_in sin);
int do_upload(int cfd,struct sockaddr_in sin);
#define ERR_MSG(msg) do{\
printf("line:%d\n",__LINE__);\
perror(msg);\
}while(0)
#define PORT 69
#define N 516
int main(int argc, const char *argv[])
{
if(argc<2)
{
printf("please input ip:\n");
return -1;
}
//创建流式套接字
int cfd = socket(AF_INET,SOCK_DGRAM,0);
if(cfd<0)
{
ERR_MSG("socket");
return -1;
}
//功能:允许端口快速被重用,快速被复用
int reuse = 1;
if(setsockopt(cfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0)
{
ERR_MSG("setsockopt");
return -1;
}
//填充服务器的地址信息结构体 AF_INET:man 7 ip
struct sockaddr_in sin;
sin.sin_family =AF_INET;
sin.sin_port =htons(PORT);
sin.sin_addr.s_addr=inet_addr(argv[1]);
int choose;
while(1)
{
printf("******************************\n");
printf("*********1.download***********\n");
printf("*********2.upload*************\n");
printf("*********3.exit***************\n");
printf("******************************\n");
printf("please input the option:\n");
scanf("%d",&choose);
getchar();
switch(choose)
{
case 1:
do_download(cfd,sin);
break;
case 2:
do_upload(cfd,sin);
break;
case 3:
goto END;
break;
default:
printf("input fasle\n");
}
}
END:
close(cfd);
return 0;
}
//download
int do_download(int cfd,struct sockaddr_in sin)
{
char filename[20]="";
printf("please input the name of file which needs to download:");
fgets(filename,sizeof(filename),stdin);
filename[strlen(filename)-1] = 0;
//发送下载请求包给服务器
char buf[N]="";
int size = sprintf(buf,"%c%c%s%c%s%c",0,1,filename,0,"octet",0);
if(sendto(cfd,buf,size,0,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("sendto");
return -1;
}
//循环接收发送应答包
ssize_t recv_len;
socklen_t len1 = sizeof(sin);
int flag = 0;
unsigned short num = 1;
int fd =-1;
while(1)
{
bzero(buf,sizeof(buf));
recv_len = recvfrom(cfd,buf,N,0,(struct sockaddr*)&sin,&len1);
if(recv_len<0)
{
ERR_MSG("recvfrom");
return -1;
}
if(3==buf[1]) //数据包操作码是2bytes,操作码3位于buf[1]数组高位,低位是0x00
{
if(0==flag) //在循环里边,为防止重复打开,故设置只有在第一次的时候打开文件
{
fd = open(filename,O_CREAT|O_WRONLY|O_TRUNC,0664);
flag = 1;
}
if(htons(num)==*(unsigned short *)(buf+2)) //防止数据包重复到达
{
if(write(fd,buf+4,recv_len-4)<0)
{
ERR_MSG("write");
break;
}
//回复ACK
//数据包前四个字节除了操作码与ACK包不一样,其余全部一致
buf[1]=4; //将数据包的操作码改为ACK的操作码4,则发送数据包前四位就行
if(sendto(cfd,buf,4,0,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("send");
}
if(recv_len-2-2<512)
{
printf("download complete\n");
break;
}
num++;
}
}
else if(5==buf[1]) //错误包
{
printf("----ERROR:%s",buf+4);
break;
}
}
close(cfd);
return 0;
}
int do_upload(int cfd,struct sockaddr_in sin)
{
char filename[20]="";
printf("please input the name of file which needs to upload:");
fgets(filename,sizeof(filename),stdin);
filename[strlen(filename)-1] = 0;
//判断想上传的文件是否存在
int fd = open(filename,O_RDONLY);
if(fd<0)
{
ERR_MSG("open");
return -1;
}
//发送上传请求
char buf[N]="";
int size = sprintf(buf,"%c%c%s%c%s%c",0,2,filename,0,"octet",0);
if(sendto(cfd,buf,size,0,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("sendto");
return -1;
}
//循环接收发送数据包
ssize_t recv_len;
socklen_t len1 = sizeof(sin);
int flag = 0;
unsigned short num = 0;
while(1)
{
bzero(buf,sizeof(buf));
recv_len = recvfrom(cfd,buf,N,0,(struct sockaddr*)&sin,&len1);
if(recv_len<0)
{
ERR_MSG("recvfrom");
return -1;
}
if(4==buf[1]) //服务器返回应答包
{
//判断当前数据包的编号是否等于应答包的编号
if(num ==ntohs(*(unsigned short*)(buf+2)))
{
//修改操作码为数据包
buf[1]=3;
//填充块编号
num++;
*(unsigned short*)(buf+2) = htons(num);
//读取数据
int res = read(fd,buf+4,sizeof(buf)-4);
if(res<0)
{
ERR_MSG("read");
return -1;
}
else if(0==res)
{
printf("upload complete\n");
break;
}
//发送数据包
if(sendto(cfd,buf,res+2+2,0,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("sendto");
return -1;
}
}
else
{
printf("file upload failed\n");
break;
}
}
else if(5==buf[1])
{
printf("---ERROR:%s\n",buf+4);
break;
}
}
return 0;
}