【FTP服务器】C语言搭建FTP客户端环境(帮助理解FTP)

由于工作与学习的需要,笔者用C语言实现了一个简版的基于FTP协议的客户端,实现了诸如ls命令,cd命令,pwd命令,以及新建于删除目录,和上传本体文件/下载文件到本地的功能,功能较为简单,但对理解FTP的机制有不错的帮助。代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

//重定义socket地址类型
typedef struct sockaddr* SP;

//向服务器端发送指定序列
int _send(int cli_sock,char* buf,size_t buf_size,\
			char* ORDER,size_t ORDER_size)
{
	sprintf(buf,"%s",ORDER);		//将要发送给服务器的指令写入buf缓冲区
	send(cli_sock,buf,strlen(buf),0);	//将buf内的指令发送给服务器
}

//接受服务器端反馈信息
int _recive(int cli_sock, char* buf, size_t buf_size,size_t ret,size_t _ret)
{
	recv(cli_sock,buf,buf_size,0);		//从服务器端接受,接受内存容存入buf
	printf("recv:%s\n",buf);
	sscanf(buf,"%d",&ret);				//buf内的内容写入变量ret
	if(ret != _ret)
	{
		printf("操作失败!请检查命令并重试!\n");
		return EXIT_FAILURE;
	}
}

//显示当前目录
int _PWD(int cli_sock,char* buf,size_t buf_size,size_t ret)
{
	_send(cli_sock,buf,buf_size,"PWD\n",4);			//调用发送函数

	_recive(cli_sock,buf,buf_size,ret,257);			//调用接受函数
}

//进入指定目录
char* _CWD(int cli_sock,char* buf,size_t buf_size,size_t ret)
{
	static char path[20] = {};
	scanf("%s",path);								//定义一个输入缓冲区并接收
	sprintf(buf,"CWD %s\n",path);					//将输入缓冲区的内容写入buf
	send(cli_sock,buf,strlen(buf),0);

	_recive(cli_sock,buf,buf_size,ret,250);
	return path;
}

//创建新目录
int _MKD(int cli_sock,char* buf,size_t buf_size,size_t ret)
{
	char path[20] = {};								//原理同上
	scanf("%s",path);
	sprintf(buf,"MKD %s\n",path);
	send(cli_sock,buf,strlen(buf),0);

	_recive(cli_sock,buf,buf_size,ret,257);
}

//建立数据传输通道
int _PASV(int cli_sock,char* buf,size_t buf_size,size_t ret,\
			struct sockaddr_in cli_addr,size_t addrlen)
{
	_send(cli_sock,buf,buf_size,"PASV\n",5);		//向服务器发送PASV指令并接收返回值

	_recive(cli_sock,buf,buf_size,ret,227);
	
	unsigned char ip1,ip2,ip3,ip4,port1,port2;
	sscanf(buf,"227 Entering Passive Mode (%hhu,%hhu,%hhu,%hhu,%hhu,%hhu",&ip1,&ip2,&ip3,&ip4,&port1,&port2);    //抓取ip信息
	char ip[16] = {};
	sprintf(ip,"%hhu.%hhu.%hhu.%hhu",ip1,ip2,ip3,ip4);
	short port = port1*256+port2;                                    //计算出端口信息
	printf("ip=%s port=%hd\n",ip,port);

	int cli_pasv = socket(AF_INET,SOCK_STREAM,0);                    //建立数据传输专用socket
	if(0 > cli_pasv)
	{
		perror("socket");
		return EXIT_FAILURE;
	}
	cli_addr.sin_port = htons(port);
	cli_addr.sin_addr.s_addr = inet_addr(ip);

	if(connect(cli_pasv,(SP)&cli_addr,addrlen))
	{
		perror("connect");
		return EXIT_FAILURE;
	}
	return cli_pasv;
}

//查看当前目录详情列表
int _LS(int cli_sock,char* buf,size_t buf_size,size_t ret,\
			struct sockaddr_in cli_addr,size_t addrlen)
{
	int cli_pasv = _PASV(cli_sock,buf,buf_size,ret,cli_addr,addrlen);    //建立数据传输通道

	_send(cli_sock,buf,buf_size,"LIST -al\n",9);                    //向服务器发送指令
	char buf1[1000000] = {};
	recv(cli_pasv,buf1,sizeof(buf1),0);
	printf("%s\n",buf1);
	close(cli_pasv);

	bzero(buf,buf_size);
	_recive(cli_sock,buf,buf_size,ret,150);
	bzero(buf,buf_size);
	_recive(cli_sock,buf,buf_size,ret,226);
}

//删除目录
int _RMD(int cli_sock,char* buf,size_t buf_size,size_t ret,\
				struct sockaddr_in cli_addr,size_t addrlen)
{
	char* arr = _CWD(cli_sock,buf,buf_size,ret);
	_PWD(cli_sock,buf,buf_size,ret);

	sprintf(buf,"RMD %s\n",arr);
	send(cli_sock,buf,strlen(buf),0);
	
	_recive(cli_sock,buf,buf_size,ret,250);

	_LS(cli_sock,buf,buf_size,ret,cli_addr,addrlen);
}

//上传
int _STOR(int cli_sock,char* buf,size_t buf_size,size_t ret,\
			struct sockaddr_in cli_addr,size_t addrlen)
{
	int cli_pasv = _PASV(cli_sock,buf,buf_size,ret,cli_addr,addrlen);

	char path[20] = {};
	scanf("%s",path);
	sprintf(buf,"STOR %s\n",path);
	send(cli_sock,buf,strlen(buf),0);
	bzero(buf,buf_size);
	_recive(cli_sock,buf,buf_size,ret,150);

	int fd = open(path,O_RDONLY);                            //打开本地文件
	if(0 > fd)
	{
		perror("open");
		return EXIT_FAILURE;
	}

	while(ret = read(fd,buf,buf_size))                       //边读取边上传至服务器
	{
		send(cli_pasv,buf,ret,0);
	}
	
	close(fd);
	close(cli_pasv);

	bzero(buf,buf_size);
	_recive(cli_sock,buf,buf_size,ret,226);
}

//下载文件
int _RETR(int cli_sock,char* buf,size_t buf_size,size_t ret,\
			struct sockaddr_in cli_addr,size_t addrlen)
{
	char path[20] = {};
	scanf("%s",path);
	sprintf(buf,"SIZE %s\n",path);
	send(cli_sock,buf,strlen(buf),0);

	_recive(cli_sock,buf,buf_size,ret,213);

	sprintf(buf,"MDTM %s\n",path);
	send(cli_sock,buf,strlen(buf),0);

	_recive(cli_sock,buf,buf_size,ret,213);

	int cli_pasv = _PASV(cli_sock,buf,buf_size,ret,cli_addr,addrlen);

	sprintf(buf,"RETR %s\n",path);
	send(cli_sock,buf,strlen(buf),0);
	
	int fd = open(path,O_RDWR|O_CREAT|O_TRUNC, 0666);            //以写和创建的方式打开一个本地文件
	if(0 > fd)
	{
		perror("open");
		return EXIT_FAILURE;
	}

	size_t ret_size = 0;
	
	while(ret_size = recv(cli_pasv,buf,buf_size,0))            //边写入边下载
	{
		write(fd,buf,ret_size);
	}

	close(cli_pasv);

	bzero(buf,buf_size);
	_recive(cli_sock,buf,buf_size,ret,150);
	bzero(buf,buf_size);
	_recive(cli_sock,buf,buf_size,ret,226);
}


//结束ftp进程
int _QUIT(int cli_sock,char* buf,size_t buf_size,size_t ret)
{
	_send(cli_sock,buf,buf_size,"QUIT\n",5);

	_recive(cli_sock,buf,buf_size,ret,221);
}

//主函数
int main(int argc,const char* argv[])
{
	int cli_sock = socket(AF_INET,SOCK_STREAM,0);
	if(0 > cli_sock)
	{
		perror("socket");
		return EXIT_FAILURE;
	}

	struct sockaddr_in cli_addr = {};
	cli_addr.sin_family = AF_INET;
	cli_addr.sin_port = htons(21);
	cli_addr.sin_addr.s_addr = inet_addr("ip");            //输入你的目标ip
	socklen_t addrlen = sizeof(cli_addr);

	if(connect(cli_sock,(SP)&cli_addr,addrlen))
	{
		perror("connect");
		return EXIT_FAILURE;
	}

	char buf[4096] = {};
	size_t buf_size = sizeof(buf),ret = 0;

	_recive(cli_sock,buf,buf_size,ret,220);
	printf("连接服务器成功\n");

	char name[20] = {};
	printf("请输入用户名:");                            //向服务器端发送用户名
	scanf("%s",name);
	sprintf(buf,"USER %s\n",name);
	send(cli_sock,buf,strlen(buf),0);

	_recive(cli_sock,buf,buf_size,ret,331);             //验证用户名是否正确

	char pass[20] = {};
	printf("请输入密码:");                              //向服务器发送密码
	scanf("%s",pass);
	sprintf(buf,"PASS %s\n",pass);
	send(cli_sock,buf,strlen(buf),0);
	
	_recive(cli_sock,buf,buf_size,ret,230);            //验证
	printf("登录成功!\n");

	_PWD(cli_sock,buf,buf_size,ret);

	char arr[20] = {};
	printf("Remote system type is UNIX.\nUsing binary mode to transfer files.\n");
	for(;;)
	{
		printf("ftp>>>");
		scanf("%s",arr);
		if(0 == strcmp(arr,"pwd"))
			_PWD(cli_sock,buf,buf_size,ret);                //pwd命令
		if(0 == strcmp(arr,"cd"))
			_CWD(cli_sock,buf,buf_size,ret);                //cd命令
		if(0 == strcmp(arr,"mk"))
			_MKD(cli_sock,buf,buf_size,ret);                //新建目录命令
		if(0 == strcmp(arr,"rm"))
			_RMD(cli_sock,buf,buf_size,ret,cli_addr,addrlen);    //删除目录命令
		if(0 == strcmp(arr,"ls"))
			_LS(cli_sock,buf,buf_size,ret,cli_addr,addrlen);     //ls命令
		if(0 == strcmp(arr,"stor"))
			_STOR(cli_sock,buf,buf_size,ret,cli_addr,addrlen);    //上传命令
		if(0 == strcmp(arr,"retr"))
			_RETR(cli_sock,buf,buf_size,ret,cli_addr,addrlen);    //下载命令
		if(0 == strcmp(arr,"bye"))
		{
			_QUIT(cli_sock,buf,buf_size,ret);                    //bye命令
			return 0;
		}
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_48994377/article/details/108507021