基于UDP的TFTP文件传输

代码实现:

//tftp客户端
#include 

#define ERR_LOG(msg) do{\
	perror(msg);\
	printf("%d %s %s\n",__LINE__,__func__,__FILE__);\	
}while(0)

#define PORT 69

//下载功能函数
int do_download(int sfd,struct sockaddr_in ser)
{
	char filename[20]="";
	printf("请输入要下载的文件名:");
	fgets(filename,20,stdin);
	filename[strlen(filename)-1]='\0';

	//发送下载请求
	char buf[516]="";
	int size=sprintf(buf,"%c%c%s%c%s%c",0,1,filename,0,"octet",0);
	sendto(sfd,buf,size,0,(struct sockaddr*)&ser,sizeof(ser));

	int flag=0;
	int fd;

	//循环接收发送应答包
	int recv_len;
	unsigned short num=1;
	socklen_t addrlen=sizeof(ser);
	while(1)
	{
		bzero(buf,516);
		recv_len=recvfrom(sfd,buf,516,0,(struct sockaddr*)&ser,&addrlen);
		if(recv_len < 0)
		{
			ERR_LOG("recvfrom");
			return -1;
		}

		if(buf[1]==3)  //如果是数据包
		{
			if(flag==0)  //防止文件重复打开
			{
				fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0664);
				flag=1;
			}

			//判断当前的快编号,是否与期望的快编号一致
			if( htons(num) == *(unsigned short*)(buf+2) )  //防止数据包重复到达
			{
				if(write(fd,buf+4,recv_len-4) < 0)
				{
					printf("fd:%d recv_len=%ld\n",fd,recv_len);
					ERR_LOG("write");
					break;
				}

				//回复ack包
				//由于数据包前四个字节与ack包除了操作码不一样,其余全部一致
				//发送数据包的前四个字节
				buf[1]=4;
				sendto(sfd,buf,4,0,(struct sockaddr*)&ser,sizeof(ser));

				//判断数据包的大小是否小于 512+2+2
				if(recv_len < 512+2+2)
				{
					printf("-----文件下载完成!-----\n");
					break;
				}
				num++;
			}
		}
		else if(buf[1]==5)  //错误包
		{
			//打印错误信息
			printf("---ERROR:%s---\n",buf+4);
			break;
		}
	}

	return 0;
}

//上传功能函数
int do_upload(int sfd,struct sockaddr_in ser)
{
	char filename[20]="";
	printf("请输入要上传的文件名:");
	fgets(filename,20,stdin);
	filename[strlen(filename)-1]='\0';

	//判断该文件是否存在
	int fd=open(filename,O_RDONLY);
	if(fd < 0)
	{
		if(errno==ENOENT)
		{
			printf(">>>文件不存在,请重新输入<<<\n");
			return -2;
		}
		else
		{
			ERR_LOG("open");
			return -1;
		}
	}

	//发送上传请求
	//上传协议
	char buf[516]="";
	int size=sprintf(buf,"%c%c%s%c%s%c",0,2,filename,0,"octet",0);
	sendto(sfd,buf,size,0,(struct sockaddr*)&ser,sizeof(ser));

	//循环接收发送数据包
	int recv_len;
	unsigned short num=0;
	socklen_t addrlen=sizeof(ser);
	while(1)
	{
		bzero(buf,516);
		recv_len=recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&ser,&addrlen);
		if(recv_len < 0)
		{
			ERR_LOG("recvfrom");
			return -1;
		}

		//操作码的范围是1-5,因为是网络字节序
		//所以有效操作码存储在高位,即buf[1]的位置
		if(buf[1]==4)
		{
			//判断当前数据包的编号是否等于应答包的编号
			//防止数据包在传送过程中丢包或者重复收包
			if( num==ntohs(*(unsigned short*)(buf+2)) )
			{
				//修改操作码为数据包
				buf[1]=3;
				//填充块编号
				num++;
				*(unsigned short*)(buf+2)=htons(num);

				//读取数据
				//发送数据
				int res=read(fd,buf+4,516-4);
				if(res < 0)
				{
					ERR_LOG("read");
					return -1;
				}
				else if(res==0)
				{
					printf("-----文件上传完毕-----\n");
					break;
				}
				//发送数据包
				sendto(sfd,buf,res+4,0,(struct sockaddr*)&ser,sizeof(ser));
			}
			else
			{
				printf("-----文件上传失败,请检查网络环境-----\n");
				break;
			}
		}
		else if(buf[1]=5)
		{
			printf("---ERROR:%s---\n",buf+4);
			break;
		}
	}

	return 0;
}

int main(int argc, const char *argv[])
{ 
	if(argc != 2)
	{
		printf("input error\n");
		printf("usage:./a.out ip\n");
		return -1;
	}

	int sfd=socket(AF_INET,SOCK_DGRAM,0);

	//填充服务器地址信息结构体
	struct sockaddr_in ser;
	ser.sin_family=AF_INET;
	ser.sin_port=htons(PORT);
	ser.sin_addr.s_addr=inet_addr(argv[1]);
	socklen_t socklen=sizeof(ser);

	int menu=-1;
	while(1)
	{
		system("clear");
		printf("\t=====1、下载=====\n");
		printf("\t=====2、上传=====\n");
		printf("\t=====0、退出=====\n");

		printf("请输入功能选项:");
		scanf("%d",&menu);
		getchar();  //吸收回车

		switch(menu)
		{
		case 1:
			{
				do_download(sfd,ser);
			}
			break;
		case 2:
			{
				do_upload(sfd,ser);
			}
			break;
		case 0:goto END;
		default:printf("input error,try again!\n");
		}
		//阻塞
		printf("输入任意键,按回车清空:");
		while(getchar() != '\n');
	}

END:

	close(sfd);

	return 0;
}

你可能感兴趣的:(网络,服务器,linux)