1、实现TFTP的客户端,下载服务器的文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"__%d__\n",__LINE__);\
perror(msg);\
}while(0)
#define N 600
int main(int argc, const char *argv[])
{
if(argc<3){
fprintf(stderr,"请传参:%s ip filename\n",argv[0]);
return -1;
}
//创建套接字
int cfd=socket(AF_INET,SOCK_DGRAM,0);
if(cfd<0){
ERR_MSG("socket");
return -1;
}
printf("socket success\n");
//允许端口快速重用
int reuse;
if(setsockopt(cfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0){
ERR_MSG("setsockopt");
return -1;
}
//填充地址结构体
struct sockaddr_in sin;
sin.sin_family=AF_INET;
sin.sin_port=htons(69);
sin.sin_addr.s_addr=inet_addr(argv[1]);
//保存与客户端通信的目标地址信息结构体
struct sockaddr_in tar;
socklen_t addrlen=sizeof(tar);
//使用字符数组当消息包载体
//组一个下载请求包
char buf[N]={0};
char *prt=buf;
int len;
*(short *)prt=htons(1);
prt=buf+2;
strcpy(prt,argv[2]);
prt+=strlen(prt)+1;
strcpy(prt,"octet");
prt+=strlen(prt);
len=prt-buf+1;
//给服务器发送下载请求
if(sendto(cfd,buf,len,0,(struct sockaddr *)&sin,sizeof(sin))<0){
ERR_MSG("sendto");
return -1;
}
//在本地创建一个文件保存下载的文件内容
int fd=open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0664);
if(fd<0){
ERR_MSG("open");
return -1;
}
//下载文件
ssize_t res=0;
//记录消息包的编号
short num=1;
while(1){
//接受服务器的数据包
res=recvfrom(cfd,buf,516,0,(struct sockaddr *)&tar,&addrlen);
if(res<0){
ERR_MSG("recvfrom");
break;
}
//判断数据包是否有误
if(ntohs(*(short *)buf)!=3){
fprintf(stderr,"%s",buf+4);
continue;
}
//判断数据包编号是否连续
if(ntohs(*(short *)(buf+2))!=num){
continue;
}
//写入文件
write(fd,buf+4,res-4);
//判断是否下载完毕
if(res<516){
printf("下载完毕\n");
break;
}
//组ACK
*(short *)buf=htons(4);
*(short *)(buf+2)=htons(num++);
//向服务器发送ACK,表示想要接受下一个消息包
if(sendto(cfd,buf,4,0,(struct sockaddr *)&tar,addrlen)<0){
ERR_MSG("sendto");
break;
}
}
//关闭套接字和文件描述符
close(cfd);
close(fd);
return 0;
}
测试:
2、实现TFTP的客户端,上传本地文件到服务器
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"__%d__\n",__LINE__);\
perror(msg);\
}while(0)
#define N 600
int main(int argc, const char *argv[])
{
if(argc<3){
fprintf(stderr,"请传参:%s ip filename\n",argv[0]);
return -1;
}
//创建套接字
int cfd=socket(AF_INET,SOCK_DGRAM,0);
if(cfd<0){
ERR_MSG("socket");
return -1;
}
printf("socket success\n");
//允许端口快速重用
int reuse;
if(setsockopt(cfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0){
ERR_MSG("setsockopt");
return -1;
}
//填充地址结构体
struct sockaddr_in sin;
sin.sin_family=AF_INET;
sin.sin_port=htons(69);
sin.sin_addr.s_addr=inet_addr(argv[1]);
//保存与客户端通信的目标地址信息结构体
struct sockaddr_in tar;
socklen_t addrlen=sizeof(tar);
//使用字符数组当消息包载体
//组一个上传请求包
char buf[N]={0};
char *prt=buf;
int len;
*(short *)prt=htons(2);
prt=buf+2;
strcpy(prt,argv[2]);
prt+=strlen(prt)+1;
strcpy(prt,"octet");
prt+=strlen(prt);
len=prt-buf+1;
//给服务器发送上传请求
if(sendto(cfd,buf,len,0,(struct sockaddr *)&sin,sizeof(sin))<0){
ERR_MSG("sendto");
return -1;
}
//接受服务器的消息,获取临时ip与端口号
if(recvfrom(cfd,buf,4,0,(struct sockaddr *)&sin,&addrlen)<0){
ERR_MSG("recvfrom");
return -1;
}
//打开本地要上传到服务器的文件
int fd=open(argv[2],O_RDONLY);
if(fd<0){
ERR_MSG("open");
return -1;
}
//上传文件
ssize_t res=0;
//记录消息包的编号
short num=1;
while(1){
bzero(buf,sizeof(buf));
//组消息包
*(short *)buf=htons(3);
*(short *)(buf+2)=htons(num);
//从本地文件中读数据存到消息包中
res=read(fd,buf+4,512);
if(res<0){
ERR_MSG("read");
break;
}
TRY:
//上传消息包
if(sendto(cfd,buf,res+4,0,(struct sockaddr *)&sin,sizeof(sin))<0){
ERR_MSG("sendto");
break;
}
//接受服务器的ACK
if(recvfrom(cfd,buf,4,0,(struct sockaddr *)&sin,&addrlen)<0){
ERR_MSG("recvfrom");
break;
}
//判断ACK是否有误
if(ntohs(*(short *)buf)!=4){
goto TRY;
}
if(ntohs(*(short *)(buf+2))!=num){
goto TRY;
}
//判断是否上传完毕
if(res<512){
printf("上传完毕\n");
break;
}
num++;
}
//关闭套接字和文件描述符
close(cfd);
close(fd);
return 0;
}
测试: