基于UDP的TFTP文件传输
功能:下载、上传、退出
#include <myhead.h>
#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'就是后面那个0char* 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++;}
}