TFTP协议 下载功能
实现代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
//打印错误新的宏函数
#define ERR_MSG(msg) do{\
fprintf(stderr, " __%d__ ", __LINE__);\
perror(msg);\
}while(0)
int main(int argc, const char *argv[])
{
if(argc < 4)
{
fprintf(stderr, "请输入IP port\n");
return -1;
}
//将获取到的端口号字符串,转换成整形
int port = atoi(argv[2]);
//创建报式套接字
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
//绑定客户端自身的地址信息结构体 ---> 非必须绑定
//填充服务器的IP地址以及端口号 -->因为客户端要主动发送数据包给服务器
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = inet_addr(argv[1]);
struct sockaddr_in rcv_addrmsg; //存储接收到的数据包来自哪里
socklen_t addrlen = sizeof(rcv_addrmsg);
char buf[128] = ""; //读写请求
char buf2[1024]= ""; //数据包
char buf3[16]=""; //ACK
char *ptr=buf;
short int* pa =(short int*)ptr;
*pa=htons(1); //读写请求的操作码
char *pb=ptr+2; //读写请求的文件名
strcpy(pb,"5.png");
char *pc =pb+strlen(pb);
char *pd=pc+1;
strcpy(pd,"octet"); //读写请求的模式
size_t size=2+strlen(pb)+1+strlen("octet")+1; //读写请求长度
//将数据包发送给服务器,所以地址信息结构体需要填服务器的信息
if(sendto(sfd, buf,size,0,(struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
//printf("sendto success\n");
short int num=1; //块编号
ssize_t res; //数据包成功读取的字节数
int fd=open(argv[3],O_WRONLY|O_TRUNC|O_CREAT,0777); //打开指定文件
if(fd<0)
{
ERR_MSG("open");
return -1;
}
while(1)
{
bzero(buf2, sizeof(buf2)); // 数据包清空
//接收服务器发送过来的数据包
if((res=recvfrom(sfd, buf2, sizeof(buf2), 0, (struct sockaddr*)&rcv_addrmsg,&addrlen))< 0)
{
ERR_MSG("recvfrom");
return -1;
}
char *ptr2=buf2;
short int *err;
if(ntohs(*(err=(short int*)ptr2))==5) //操作码等于5,ERROR
{
printf("error:%s\n",(char*)(err+2)); //打印差错信息
return -1;
}
//操作码不为5,即为3,是数据包,ptr2指向数据包首地址
write(fd,ptr2+4,res-4); //数据包的数据写入文件
short int *pa2=(short int*)ptr2; //获取服务器发过来的块编号
if(ntohs(*(pa2+1))!=num) //比较块编号是否相同,即顺序正确
{
continue;
}
num++;
bzero(buf3, sizeof(buf3)); //清空ACK
char *ptr3=buf3; //ACK首地址
short int* pa3=(short int*)ptr3;
*pa3=htons(4); //ACK操作码
*(pa3+1)=*(pa2+1); //ACK块编号
//将ACK发送给服务器
if(sendto(sfd, buf3,4,0,(struct sockaddr*)&rcv_addrmsg, addrlen) < 0)
{
ERR_MSG("sendto");
return -1;
}
if(res<516) //数据包里的数据小于512字节
{
break; //退出循环
}
}
printf("下载完成\n");
//关闭套接字
close(sfd);
return 0;
}
运行结果
服务器
IP地址:192.168.31.22
端口:69
从服务器下载文件5.png