基于UDP的TFTP文件传输
功能:下载、上传、退出
#include
#define IP "192.168.8.100"
#define PORT 69
int download_file(int cfd,struct sockaddr_in sin);
int upload_file(int cfd,struct sockaddr_in sin);
int main(int argc, const char *argv[])
{
//创建套接字文件
int cfd=socket(AF_INET,SOCK_DGRAM,0);
if(cfd<0){
ERR_MSG("socket");
return -1;
}
printf("socket create success\n");
//填充地址信息体
struct sockaddr_in sin;
sin.sin_family=AF_INET;
sin.sin_port=htons(PORT);
sin.sin_addr.s_addr=inet_addr(IP);
//定义输入内容调用的函数
char choose=0;
while(1){
printf("--------------------\n");
printf("-------1.下载-------\n");
printf("-------2.上传-------\n");
printf("-------3.退出-------\n");
printf("--------------------\n");
scanf("%c",&choose);
while(getchar()!='\n'); //当不为\n时循环
switch(choose){
case '1':
download_file(cfd,sin);
break;
case '2':
upload_file(cfd,sin);
break;
case '3':
goto END;
default:
printf("输入错误请重新输入\n");
}
}
END:
//关闭文件
close(cfd);
return 0;
}
包的结构:
下载:
1、拼接 读操作 请求体,向服务器发送
2、服务器返回数据包,保存其中的块编号,并通过操作码判断是否是数据包,打印出错误码
3、将数据包的数据写入本地文件中
4、返回ACK包,ACK块编号为数据包的块编号
5、直到数据包小于516时,文件下载完毕
注意:ACK包总共就4个字节,发送给服务器的时候只能发4Bytes的包
//下载
int download_file(int cfd,struct sockaddr_in sin)
{
char filename[20]="";
printf("请输入需要下载的文件>>>");
scanf("%s",filename);
while(getchar()!='\n');
//组包发送请求
char buf[516]="";
short* p1=(short*)buf;
*p1=htons(1); //操作码
char* p2=buf+2;
strcpy(p2,filename);//已经包含了'\0'就是后面那个0
char* p3=p2+strlen(p2)+1;
strcpy(p3,"octet");
int size=2+strlen(p2)+1+strlen(p3)+1;
if(sendto(cfd,buf,sizeof(buf),0,\
(struct sockaddr*)&sin,sizeof(sin))<0){
ERR_MSG("sendto");
return -1;
}
printf("发送下载请求成功\n");
//收数据
struct sockaddr_in recvin;
socklen_t len=sizeof(recvin);
ssize_t res=0;
short* ack=NULL;
char Ack[4]="";
int pd=0;
//打开要写入的文件
pd=open(filename,\
O_CREAT|O_WRONLY|0664);
while(1){
bzero(buf,sizeof(buf));
if((res=recvfrom(cfd,buf,sizeof(buf),0,\
(struct sockaddr*)&recvin,&len))<0){
ERR_MSG("recvfrom");
return -1;
}
printf("res=%ld\n",res);
printf("成功接收 [%s:%d] 数据\n",\
inet_ntoa(recvin.sin_addr),ntohs(recvin.sin_port));
p1=(short*)buf; //服务器返回的操作值
ack=p1+1; //服务器返回的数据包编号
// printf("ack=%d\n",ntohs(*ack));
if(ntohs(*p1)!=3){ //数据包操作码不对
printf("数据包错误\n");
if(ntohs(*p1)==5){ //5是报错包
printf("ERRNO:%d\n",ntohs(*(p1+1)));
}
return -1;
}else{
//将接收的数据写到文件里
if(pd<0 && errno!=17){
// printf("ERR:%d\n",errno);
ERR_MSG("open");
return -1;
}
if(write(pd,buf+4,res-4)<0){
ERR_MSG("write");
return -1;
}
//返回ack包
bzero(Ack,sizeof(Ack));
p1=(short*)Ack;
*p1=htons(4);
*(p1+1)=*ack;
if(sendto(cfd,Ack,sizeof(Ack),0,\
(struct sockaddr*)&recvin,\
sizeof(recvin))<0){
ERR_MSG("sendto");
return -1;
}
printf("返回ack包=%d成功\n",ntohs(*ack));
}
if(res<516){
printf("下载完成\n");
close(pd);
break;
}
}
}
上传:
1、拼接 写操作 请求体,向服务器发送
2、服务器返回ACK包,通过操作码判断是否是ACK包,打印出错误码
3、拼接数据包,数据包编号从1开始ACK块编号是跟着数据包走的
4、将数据从本地文件中读出来,发送给服务器
5、直到从本地读出的数据小于512时,文件上传完毕
注意:用res控制包的大小,更灵活不会传空数据
//上传
int upload_file(int cfd,struct sockaddr_in sin)
{
char buf[516]="";
char name[100]="";
printf("请输入要上传的文件名>>>");
scanf("%s",name);
while(getchar()!='\n');
int pd_r=open(name,O_RDONLY);
if(pd_r<0){
ERR_MSG("open");
return -1;
}
//组装请求数据包
short* p1=(short*)buf;
*p1=htons(2);
char* p2=buf+2;
strcpy(p2,name);
char* p3=p2+strlen(name)+1;
strcpy(p3,"octet");
size_t size=2+strlen(name)+1+strlen(p3)+1;
if(sendto(cfd,buf,size,0,(struct sockaddr*)&sin,sizeof(sin))<0){
ERR_MSG("sendto");
return -1;
}
struct sockaddr_in recvin;
socklen_t len=sizeof(recvin);
short acknum=1;
ssize_t res=0;
char ack[4]="";
while(1){
//接收服务器返回数据
// bzero(buf,sizeof(buf));
if(recvfrom(cfd,ack,sizeof(ack),0,\
(struct sockaddr*)&recvin,&len)<0){
ERR_MSG("recvfrom");
return -1;
}
// printf("%d\n",ntohs(*(short*)ack)); //4 服务器给的ack包
if(ntohs(*(short*)ack)==5){
printf("请求错误,ERRNO:%d\n",ntohs(*(short*)ack+1));
return -1;
}
//发文件数据包
bzero(buf,sizeof(buf));
p1=(short*)buf;
*p1=htons(3);
*(p1+1)=htons(acknum);
res=read(pd_r,buf+4,512);
if(sendto(cfd,buf,res+4,0,\
(struct sockaddr*)&recvin,len)<0){
ERR_MSG("sendto");
return -1;
}
if(res<512){
printf("上传完成\n");
close(pd_r);
break;
}
acknum++;
}
}