FTP云盘

参考:FTP云盘项目
作者:糯米啊啊
发布时间: 2021-08-19 10:34:05
网址:https://blog.csdn.net/weixin_43732386?spm=1001.2014.3001.5509

参考:自制FTP云盘项目
作者:不说话的小脑斧
发布时间: 2021-01-13 12:02:23
网址:https://blog.csdn.net/qq_44745336/article/details/112547781?spm=1001.2014.3001.5502

以及:https://blog.csdn.net/zouchengzhi1021/article/details/113668089

项目简介

FTP服务器(File Transfer Protocol Server)是在互联网上提供文件存储和访问服务的计算机,它们依照FTP协议提供服务。 FTP是File Transfer
Protocol(文件传输协议)。
程序运行,服务端不断接收客户端指令,服务端可同时处理多个客户端接入并对指令作出解析,并把执行结果返回给客户端,客户端根据服务端对指令的解析并把由服务端传递过来的处理信息通过客户端呈现给客户,实现文件的各种操作。

可作为嵌入式面试话术使用

这个项目分成ftp客户端及服务端,实现的功能和Linux开源的ftp服务器类似,客戶端通过网络,远程获取服务端磁盘上的文件夹内容,下载文件,上传文件等功能。(基本功能描述)

ftp服务器用到的是Socket通信,当收到客户端接入的时候,创建子进程对接连接,子进程启动后分析来自客户端的指令,比如收到get file1的指令,是客户端想要获取file1文件的,我先用strstr函数进行字符串分割,获取到文件名,在判断文件是否存在,如果文件存在,就读取文件內容,再将內容通过套接字发给客户端,客户端收到数据后,创建文件,并将收到的数据写入文件,完成文件的远程下载。(说明网络编程,字符串编程,文件编程的功底)

上传文件和下载文件类似,主要还是涉及文件的操作,字符串的操作,以及网络编程。

还支持了Is’pwd,cd等Linux系统常用的指令。普通指令的实现用popen来调用系统质量,并读取执行的结构。如果不需要获取执行结果,用system函数调用就可以了。(说明popen,system的编程)

这个项目我是来锻炼我的LinUx系统编程能力的,在学习系统编程的时候,我还学习了进程间通信,如管道,信号,共享内存,消息队列等。现在正在优化这个项目,想把这块知识用到项目中去,下次遇到项目的话就比较得心应手,做开发就是要多多折腾嘛。

功能说明

本文是基于Linux网络编程实现的FTP服务器,服务器由服务端和客户端组成,具有浏览远程服务端的文件和浏览客户端本地文件,同时支持对远程服务端文件的删除,存储,归档操作处理,以及客户端对远程服务端文件的上传和下载。

利用socket实现云盘的基本功能:

  • ls———查看服务端文件
  • lls———查看客户端自己的文件
  • cd———切换服务端目录
  • lcd———切换客户端自己的目录
  • put———上传文件
  • get———下载文件
  • pwd———显示路径
  • quit———退出

代码编写

config.h

#define LS		1
#define LLS		2
#define CD		3
#define GET		4
#define PUT		5
#define PWD		6
#define QUIT	0

typedef struct msg			//传递信息的结构体
{
	int type;//没有用到
	char cmd[128];//存放命令
	char data_buf[1024];//存放文件内容
}Msg;

服务端

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

int Analysis_Command(char *buf)		//分析命令
{
     //int strcmp(const char *s1, const char *s2);//字符串比较
	if(strcmp("ls",buf)==0)  				return LS;
	if(strcmp("pwd",buf)==0)  				return PWD;
	if(strcmp("quit",buf)==0)				return QUIT;
	if(strcmp("lls",buf)==0)				return LLS;
	//char *strstr(const char *haystack, const char *needle);//搜索一个字符串在另一个字符串中的第一次出现
	if(strstr(buf,"cd")!=NULL && strstr(buf,"lcd")==NULL)	return CD;
	if(strstr(buf,"get")!=NULL)				return GET;//因为用户输入指令 带参数
	if(strstr(buf,"put")!=NULL)				return PUT;
	return -1;
}

char *getDirName(char *cmd)		//获取文件名  最好定义成 get_dir(函数名 变量名)
{
	char *fileName = NULL;
	//char *strtok(char *s, char *delim)//分解字符串 str 为一组字符串,delim 为分隔符,返回值:分隔符匹配到的第一个子串
	fileName = strtok(cmd," ");		//分割字符串
	fileName = strtok(NULL," ");	//strtok函数固定用法	
	return fileName;
}


int dowork(int *c_fd)  //函数名规范定义 msgs_Handler
{
	int cmd = 666;		//无效的值
	FILE *p_fd;			//popen的返回值类型
	int n_read;				
	char *file_name = NULL;
	Msg r_msg_buf;		//socket通信间信息的载体
	Msg w_msg_buf;		//socket通信间信息的载体
	memset(&r_msg_buf,0,sizeof(Msg));
	memset(&w_msg_buf,0,sizeof(Msg));


    // ssize_t recv(int sockfd, void *buf, size_t len, int flags);//与read功能类似
    //视频里讲 用read读完就没了 这里使用recv返回值为0判断客户端断开连接
    //ssize_t read(int fd, void *buf, size_t count);
	read(*c_fd,&r_msg_buf,sizeof(Msg));		//从socket套接字中读取命令
	cmd = Analysis_Command(r_msg_buf.cmd);	//分析命令

	switch(cmd)
	{     //客户端那边也有一样的这些指令   相互对接
		case LS:
			p_fd = popen("ls -l","r");	//调用popen函数执行 "ls-l"
			if(p_fd == NULL)		//判断是否popen成功
			{
				printf("popen error\n");
				exit(-1);
			}	
			fread(w_msg_buf.data_buf,1024,1,p_fd);//从块设备读到缓存
			write(*c_fd,&w_msg_buf,sizeof(Msg)); //缓存发给客户端,客户端调用read获得结果
			fclose(p_fd);		//关闭
			printf("get cmd : %s\n",r_msg_buf.cmd);		//服务端调试信息
			break;
		case PWD:		//pwd和ls一样
			p_fd = popen("pwd","r");//调用popen函数执行 "pwd"
			if(p_fd == NULL)
			{
				printf("popen error\n");
				exit(-1);
			}
			fread(w_msg_buf.data_buf,1024,1,p_fd);//将popen执行结果放在w_msg_buf.data_buf中
			write(*c_fd,&w_msg_buf,sizeof(Msg));//通过套接字将w_msg_buf.data_buf写到客户端吗,客户端调用read,然后输出即可获得结果
			fclose(p_fd);
			printf("get cmd : %s\n",r_msg_buf.cmd);//服务端调试信息
			break;
		case LLS:
			printf("get cmd : lls\n"); 		//lls指令是打印出客户端该目录下的文件,所以服务端不做操作,只打印一个调试信息
			break;
		case GET:
			file_name = getDirName(r_msg_buf.cmd);//获取文件名
			if(access(file_name,F_OK)==0)//通过文件名判断文件是否存在
			{
				int fd = open(file_name,O_RDWR);
				read(fd,w_msg_buf.data_buf,1024);
				write(*c_fd,&w_msg_buf,sizeof(Msg));//如果存在,即打开,读取,写入。
				close(fd);
			}
			else
			{
				strcpy(w_msg_buf.data_buf,"no this document!");//不存在则写入"no this document!"
				write(*c_fd,&w_msg_buf,sizeof(Msg));
			}
			printf("get cmd : %s %s\n",r_msg_buf.cmd,file_name);
			break;
		case CD:		//进入服务端某文件夹
			file_name = getDirName(r_msg_buf.cmd);//(分割)获取文件夹名
			if(access(file_name,F_OK)==0)//判断该文件是否存在
			{
		    	//int chdir(const char *path)//改变当前工作目录
				chdir(file_name);//系统调用函数(同cd)改变当前目录,即进入了文件夹
				//不能用system(源码是fork 另起了一个shell  这里要求自己进入文件夹)
				
				strcpy(w_msg_buf.data_buf,file_name);
				write(*c_fd,&w_msg_buf,sizeof(Msg));
			}
			else
			{
				strcpy(w_msg_buf.data_buf,"the server no have this file directory!");//如果没有则写入"the server no have this file directory!"
				write(*c_fd,&w_msg_buf,sizeof(Msg));
			}
			printf("get cmd : %s %s\n",r_msg_buf.cmd,file_name);//服务端调试信息
			break;
		case PUT:		//上传某文件到服务端
			file_name = getDirName(r_msg_buf.cmd);
			read(*c_fd,&r_msg_buf,sizeof(Msg));
			if(strcmp(r_msg_buf.data_buf, "The client no have this document!") != 0)
			{
				if(access(file_name,F_OK)==0) //如果文件存在
				{
					int fd = open(file_name,O_RDWR|O_TRUNC);//调用O_TRUNC将源文件内容删除,写入新内容
					write(fd,r_msg_buf.data_buf,strlen(r_msg_buf.data_buf));
					close(fd);
				}
				else
				{
					int fd = creat(file_name, 0666);//如果不存在,创建
					if(fd == -1)
					{
						perror("creat error!\n");
					}
					if(write(fd, r_msg_buf.data_buf, strlen(r_msg_buf.data_buf)) == -1)//写入新内容
					{
						perror("write error!\n");
					}
					close(fd);
				}
			
			}
			printf("get cmd : %s %s\n",r_msg_buf.cmd,file_name);
			break;
		case QUIT:		//客户端退出
			write(*c_fd, &w_msg_buf, sizeof(Msg));
			printf("=====client exit=====\n");
			close(*c_fd);
			exit(0);
			break;
		case -1:
			strcpy(w_msg_buf.data_buf, "Command error!");//指令错误
			write(*c_fd, &w_msg_buf, sizeof(Msg));
			printf("===== cmd error =====\n");
			break;
	}
	return 0;
}

int main(int argc,char *argv[]) 
{	

	int s_fd;
	int c_fd;

	struct sockaddr_in s_addr; 
	struct sockaddr_in c_addr;	//socket套接字所需要的结构体
	memset(&s_addr,0,sizeof(struct sockaddr_in));//清空
	memset(&c_addr,0,sizeof(struct sockaddr_in));
	int clen = sizeof(struct sockaddr_in);
	if(argc != 3){	//判断运行时传参是否正确
		printf("param error\n");
		exit(-1);
	}

	s_addr.sin_family = AF_INET;
	s_addr.sin_port = htons(atoi(argv[2]));
	inet_aton(argv[1],&(s_addr.sin_addr));

  
    //1.socket//创建套接字
    //int socket(int domain, int type, int protocol);
    
	s_fd = socket(AF_INET,SOCK_STREAM,0);
	if(s_fd == -1)
	{
		printf("socket error\n");
		perror("why");
	}
	
    //2.bind 绑定IP号及端口
    //int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

	if(bind(s_fd,(struct sockaddr *)&s_addr,clen) == -1)
	{
		printf("bind error\n");
		perror("why");
	}

     //3.lieten 监听
     // int listen(int sockfd, int backlog);
	if(listen(s_fd,10) == -1)
	{
		printf("listen error\n");
	}

	while(1){
        //4.accept 连接
        //int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
		c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
		if(c_fd == -1)
		{
			printf("accept error\n");
			perror("why");

		}
		if(fork()==0){		//有客户端连接,创建子进程来对接
			printf("***hava client***\n");
			while(1)
			{
				dowork(&c_fd);
			}
		}

	}
	close(s_fd);
	return 0;
}

客户端

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

int Analysis_Command(char *buf)//分析指令
{
	if(strcmp(buf,"ls")==0)		return LS;
	if(strcmp(buf,"lls")==0)	return LLS;
	if(strcmp(buf,"pwd")==0)	return PWD;
	if(strcmp(buf,"quit")==0)	return QUIT;
	if(strstr(buf,"cd")!=NULL && strstr(buf,"lcd")==NULL)	return CD;
	if(strstr(buf,"get")!=NULL)	return GET;
	if(strstr(buf,"put")!=NULL)	return PUT;
	return -1;
}

char *getDirName(char *cmd)//获取文件名
{
        char *file_Name = NULL;
        file_Name = strtok(cmd," ");
        file_Name = strtok(NULL," ");//strtok函数固定用法
        return file_Name;
}

int doWork(int *c_fd)
{
	int cmd = 666;//无效的值
	FILE *p_fd;//popen的返回类型为FILE*
	char *file_name = NULL;
	Msg w_msg_buf;
	Msg r_msg_buf;//套接字信息传递过程中的载体
	memset(&w_msg_buf,0,sizeof(Msg));
	memset(&r_msg_buf,0,sizeof(Msg));//清空
	printf(">>");
	gets(w_msg_buf.cmd);//获取用户输入
	write(*c_fd,&w_msg_buf,sizeof(Msg));//将用户输入的指令写入套接字中,供服务端读取
	cmd = Analysis_Command(w_msg_buf.cmd);//分析指令
	printf("*****************************\n");
	printf("\n");
	switch(cmd)
	{
		case LS:
			read(*c_fd,&r_msg_buf,sizeof(Msg));//读取客户端popen的返回内容
			printf("%s",r_msg_buf.data_buf);//打印
			printf("***********************\n");
			break;
		case PWD:
			read(*c_fd,&r_msg_buf,sizeof(Msg));//读取客户端popen的返回内容
			printf("%s",r_msg_buf.data_buf);//打印
			printf("***********************\n");
			break;
		case LLS:
			p_fd = popen("ls -l","r");//调用popen函数执行"ls-l"
			fread(r_msg_buf.data_buf,1024,1,p_fd);//客户端自己读取自己popen返回的内容
			printf("%s\n",r_msg_buf.data_buf);//打印
			fclose(p_fd);
			break;
		case GET:	//下载文件
			read(*c_fd,&r_msg_buf,sizeof(Msg));//读取客户端写入来的文件内容
			file_name = getDirName(w_msg_buf.cmd);//获取文件名
			if(strcmp(r_msg_buf.data_buf,"no this document!")==0)
			{
				printf("%s\n",r_msg_buf.data_buf);
			}
			else
			{
				if(access(file_name,F_OK)==0)//判断文件是否存在
				{
					int fd = open(file_name,O_RDWR|O_TRUNC);//存在即打开,O_TRUNC作用是将源文件内容全部删除以方便写入新内容
					if(fd==-1){
						printf("open error!\n");
						perror("why");
					}
					else
					{
						int w_ret = write(fd,r_msg_buf.data_buf,strlen(r_msg_buf.data_buf));//写入内容
						if(w_ret==-1)
						{
							printf("write error!\n");
							perror("why");
						}
						close(fd);
					}
				}
				else
				{
					int fd = creat(file_name, 0666);//不存在即创建
					if(fd == -1)
					{
						perror("creat error: ");
					}
					if(write(fd, r_msg_buf.data_buf, strlen(r_msg_buf.data_buf)) == -1)//写入
					{
						perror("write error: ");
					}
					close(fd);
				}
				printf("%s download success!\n",file_name);//提示下载成功
			}
			break;
		case CD:	//进入某文件夹
			read(*c_fd,&r_msg_buf,sizeof(Msg));
			if(strcmp(r_msg_buf.data_buf,"the server no have this file directory!")==0)
			{
				printf("%s\n",r_msg_buf.data_buf);
			}
			else
			{
				printf("enter %s\n",r_msg_buf.data_buf);
			}
			printf("get cmd: CD\n");
			break;
		case PUT:	//上传某文件至服务端
			file_name = getDirName(w_msg_buf.cmd);
			if(access(file_name, F_OK) == 0)
			{
				int fd = open(file_name, O_RDWR);
				read(fd, w_msg_buf.data_buf, 1024);
				write(*c_fd, &w_msg_buf, sizeof(Msg));
				close(fd);
			}
			else
			{
				strcpy(w_msg_buf.data_buf,"The client no have this document!");
				write(*c_fd,&w_msg_buf,sizeof(Msg));
				printf("%s\n",w_msg_buf.data_buf);
			}
			printf("get cmd : put %s\n",file_name);
			break;
		case QUIT:
			exit(0);
			break;
		default :
			read(*c_fd, &r_msg_buf, sizeof(Msg));
			printf("%s\n", r_msg_buf.data_buf);
			break;
	}

	return 0;
}

int main(int argc,char *argv[])
{
	int c_fd;
	struct sockaddr_in c_addr;

	if(argc != 3)
	{
		printf("param error\n");
		exit(-1);
	}

	int clen = sizeof(struct sockaddr);
	memset(&c_addr,0,sizeof(struct sockaddr_in));

    //1.socket//创建套接字
    //int socket(int domain, int type, int protocol);
	c_fd = socket(AF_INET,SOCK_STREAM,0);
	if(c_fd == -1)
	{
		printf("client socket error\n");
		perror("why");
	}

	c_addr.sin_family = AF_INET;
	c_addr.sin_port = htons(atoi(argv[2]));
	inet_aton(argv[1],&(c_addr.sin_addr));
	
    //2.connect 连接//客户连接主机
    //int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
	if(connect(c_fd,(struct sockaddr *)&c_addr,clen) == -1)
	{
		printf("conncet error\n");
		perror("why");
	}
	while(1){
		doWork(&c_fd);
	}
	return 0;
}

运行效果

在这里插入图片描述

V2.0版 – 启用副服务器

在这里插入图片描述

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

行稳方能走远

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值