IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux系统编程项目——FTP云盘 -> 正文阅读

[系统运维]Linux系统编程项目——FTP云盘

一、项目实现功能

  • 在客户端对服务端的操作:
    1.获取服务器的文件(get)
    2.查看服务器当前路径下的所有文件(ls)
    3.进入服务器的某个路径(cd)
    4.上传文件到服务器(put)
    5.查看服务器当前在那个文件夹(pwd)
    6.退出 (quit)

  • 在客户端本地的功能实现:
    1.查看客户端本地当前路径有哪些文件(lls)
    2.进入客户端的某个路径(lcd)
    3.查看客户端当前在那个文件夹下(lpwd)
    4.退出连接(quit)

二、项目实现思路

  • 服务器:

1.创建套接字(socket)
2.将socket与IP地址和端口绑定(bind)
3.监听被绑定的端口(listen)
4.接收连接请求(accept)
5.当有客户端接入时创建子进程对接并处理客户端发来的请求

  • 客户端:

1.创建套接字(socket)
2.连接指定计算机的端口(connect)
3.接收输入向socket中写入信息(write)
4.获取客户端发送的内容像ls,pwd,get指令,需要进行(read)

  • 指令的实现思路

lspwd:调用popen函数,执行命令,并获取执行命令后的输出内容,将内容发回到客户端

cd:将cd指令发送到服务器,在服务器端调用chdir实现路径的切换

get:客户端获取服务器的某个文件,服务器首先通过access函数判断文件是否存在,存在则将文件的内容发送到客户端,客户端创建文件,并将服务器发送的内容保存至文件中,实现get指令。

put:客户端向服务器发送文件,客户端首先通过access函数判断文件是否存在,存在则将文件的内容发送到服务器,服务器创建文件,并将客户端发送的内容保存至文件中,实现put指令。

llslpwd:调用system函数即可。

lcd:在客户端直接调用chdir函数即可

三、项目用到的函数

1.access函数(判断文件是否存在的函数)

  • 函数原型:
 int access(const char *pathname, int mode);
  • 函数说明:
    判断参数中的文件民是否存在
  • 参数:
    pathname:需要检测的文件路径名
    mode:需要测试的操作模式
    R_OK 测试读许可权
    W_OK 测试写许可权
    X_OK 测试执行许可权
    F_OK 测试文件是否存在
  • 返回值:成功执行时,返回0,失败返回-1,errno被设为以下的某个值

2.chdir函数(进入某个文件夹的函数)

  • 函数原型:
int chdir(const char *path);
  • 函数说明:切换到参数所设置的路径
  • 参数:所指代的工作目录(即将要进入的工作目录)
  • 返回值:成功返回0,失败返回-1,errno为错误代码

3.fgets函数(可以获取空格,且较为安全)

  • 函数原型:
char *fgets(char *s, int size, FILE *stream);
  • 函数说明:
    虽然用 gets() 时有空格也可以直接输入,但是 gets() 有一个非常大的缺陷,即它不检查预留存储区是否能够容纳实际输入的数据,换句话说,如果输入的字符数目大于数组的长度,gets 无法检测到这个问题,就会发生内存越界,所以编程时建议使用 fgets()。但是调用次函数会把回车\n也作为字符串的一部分,因此需要将去掉换行符删掉:
if(msg.cmd[strlen(msg.cmd) - 1] == '\n'){  			
	msg.cmd[strlen(msg.cmd) - 1] = '\0'; 
 }
  • 参数:
    s:代表要保存到的内存空间的首地址,可以是字符数组名,也可以是指向字符数组的字符指针变量名
    size:代表的是读取字符串的长度
    stream:表示从何种流中读取,可以是标准输入流 stdin,也可以是文件流,即从某个文件中读取
  • 返回值:返回该数组的指针(首地址),如果读完,则返回空指针NULL

四、代码

  • 服务端
#include <stdio.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include <sys/stat.h>
#include <fcntl.h>

char *get_cmd_dir(char *cmd)
{
	char *p=NULL;
	p=strtok(cmd," ");
	p=strtok(NULL," ");
	return p;
}//用此函数将用户输入的指令中的文件名分割开来 如cd xx,将得到xx


int get_cmd_type(char *cmd)
{
	if(!strcmp("ls",cmd)) return LS;
	if(!strcmp("pwd",cmd)) return PWD;
	if(!strcmp("quit",cmd)) return QUIT;

	
	if(strstr(cmd,"get")!=NULL) return GET;
	if(strstr(cmd,"cd")!=NULL) return CD;
	if(strstr(cmd,"put")!=NULL) return PUT;
	

	return -1;

}//用此函数将用户输入的指令对应与之相应的宏,便于之后对指令进行判断处理
void message_handler(struct Msg msg,int fd)//指令处理函数
{
	int ret;
	char *dir;
	char *fileName;
	int fdFile;
	int getSize;
	char *dataBuf;
	
	printf("get cmd: %s\n",msg.cmd);//打印客户端传来的指令

	ret =get_cmd_type(msg.cmd);

	switch (ret){
		case LS:
		case PWD:
			msg.type=0;
			//用popen()函数,获取运行的结果,可以存放在字符串当
			FILE *fp=popen(msg.cmd,"r");
			fread(msg.data,sizeof(msg.data),1,fp);//用popen函数打开的文件可以用fread函数读到相应的地方
			write(fd,&msg,sizeof(msg));//将数据发回到客户端
			pclose(fp);
			break;
		case CD:
			msg.type=1;
			dir=get_cmd_dir(msg.cmd);//首先需要获取cd 后的文件名字,这里勇封装函数获取文件名字	
			chdir(dir);//调用chdir指令,相当于cd
			break;
		case GET:
			fileName=get_cmd_dir(msg.cmd);//获取需要拷贝的文件名
			if(access(fileName,F_OK)==-1){//用access 函数判断文件是否存在,若不存在返回-1
				strcpy(msg.data,"This File is not exist");
				write(fd,&msg,sizeof(msg));//将不存在的提升发回客户端
			}else{
				msg.type=DOFILE;//若存在,则执行对文件的拷贝
				fdFile=open(fileName,O_RDWR);//存在打开文件
				getSize = lseek(fdFile,0,SEEK_END);
				lseek(fdFile,0,SEEK_SET);
				read(fdFile,msg.secondBuf,getSize);//把文件内容写入secondBuf中
				close(fdFile);
				write(fd,&msg,sizeof(msg));//将文件内容存放在结构体中并发给客户端
				
			}			
			break;
		case PUT://客户端上传文件到服务器,客户端函数与服务器的GET函数类似,将要上传的文件内容拷贝到结构体中
			fdFile=open(get_cmd_dir(msg.cmd),O_RDWR|O_CREAT,0666);//服务器首先创建并打开要上出的文件
			write(fdFile,msg.secondBuf,sizeof(msg.secondBuf));//然后将客户端上传给服务器文件里的内容拷贝到新创建的文件中
			close(fdFile);
			break;
		case QUIT://客户端发送quit指令,服务器得到指令后打印客户端退出
			printf("client quit\n");
			exit(-1);
		
		
	}
}

int main(int argc,char * argv[])
{
	int socket_fd;
	int connect_fd;
	int nread;
	int clen;//struct sockaddr_in 结构体长度
	char readBuf[128];
	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;
	struct Msg message;

	if(argc != 2){
		printf("输入端口号");
		exit(-1);
	}
	
	memset(&s_addr,0,sizeof(struct sockaddr));
	memset(&c_addr,0,sizeof(struct sockaddr));
	memset(&message,0,sizeof(struct Msg));

	socket_fd=socket(AF_INET,SOCK_STREAM,0);//创建套接字
	if(socket_fd==-1){
		perror("socket");
		exit(-1);
	}

	s_addr.sin_family=AF_INET;
	s_addr.sin_port=htons(atoi(argv[1]));

	inet_aton(" 127.0.0.1",&(s_addr.sin_addr));
	bind(socket_fd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in));//为套接字添加信息


	listen(socket_fd,10);//监听,等待客户端连接

	clen=sizeof(struct sockaddr_in);
	while(1){
		connect_fd=accept(socket_fd,(struct sockaddr*)&c_addr,&clen);//不断的检测是否有客户端接入,若有就创建一个子进程对接接入的客户端
		if(connect_fd==-1){

			perror("accept");
		}

		printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));

			if(fork()==0){//创建子进程进行对客户端的对接
				while(1){
					memset(&message,0,sizeof(message));//首先将用来传输数据的结构体里的内容置0
					nread=read(connect_fd,&message,sizeof(message));//读取客户端向服务器传送的数据结构体
					if(nread==0){
						printf("client out\n");
						break;
					}else if(nread>0){
						message_handler(message,connect_fd);//将读取出来的数据送入此函数处理						
					}
				}
			}
	
	}		
	close (connect_fd);
	close(socket_fd);
	return 0;
}


  • 客户端
#include <stdio.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include <sys/stat.h>
#include <fcntl.h>


char *get_cmd_dir(char *cmd)
{
	char *p=NULL;
	p=strtok(cmd," ");
	p=strtok(NULL," ");
	return p;
}//用此函数将用户输入的指令中的文件名分割开来 如cd xx,将得到xx

int get_cmd_type(char *cmd)
{
	if(strcmp("lls",cmd)==0)	return LLS;
	if(strstr(cmd,"lcd")!=NULL) return LCD;//注意,lcd包含cd需要在cd之前
	if(strcmp("ls",cmd)==0)		return LS;
	if(strcmp("pwd",cmd)==0)	return PWD;
	if(strcmp("quit",cmd)==0)	return QUIT;
	if(strstr(cmd,"cd")!=NULL)	return CD;
	if(strstr(cmd,"get")!=NULL) return GET;
	if(strstr(cmd,"put")!=NULL) return PUT;
	if(!strcmp(cmd,"lpwd"))     return LPWD;

	return -1;
	
}//用此函数得到客户端输入指令的宏,指令处理函数的判断

int cmd_handler(struct Msg msg,int fd)
{
	int ret;
	char *dir;
	char cmdTemp[128];
	int fdFile;
	int putSize;
	ret=get_cmd_type(msg.cmd);
		
	switch (ret)
		{
		case LS:
		case PWD:
		case CD:
		case GET:
			write(fd,&msg,sizeof(msg));			
			
			break;//当输入的指令需要在服务器上完成时,将指令直接发送给服务器

		case PUT://与服务器对于GET指令处理的方法类似
			strcpy(cmdTemp,msg.cmd);//防止strok破坏msg.cmd
			dir=get_cmd_dir(cmdTemp);
			printf("cmd :%s\n",msg.cmd);
			if(access(dir,F_OK)==-1){//用access 函数判断文件是否存在
				printf("%s this file no exit\n",dir);
				
			}else{
				fdFile=open(dir,O_RDWR);//存在打开文件
				putSize = lseek(fdFile,0,SEEK_END);//获取文件的字节数
				lseek(fdFile,0,SEEK_SET);
				read(fdFile,msg.secondBuf,putSize);
				close(fdFile);
				write(fd,&msg,sizeof(msg));
				
			}	
			
			break;
		case LCD://客户端的cd指令
			dir=get_cmd_dir(msg.cmd);
			printf("the dir:%s\n",dir);
			chdir(dir);
			break;
		case LLS://客户端的lls指令
			system("ls");
			break;
		case QUIT:
			strcpy(msg.cmd,"quit");
			write(fd,&msg,sizeof(msg));	
			close(fd);
			exit(-1);										
		case LPWD:
			system("pwd");
			break;
		}
	return ret;
	

}
void c_msg_handler(struct Msg msg,int fd)//信息处理函数,显示结果
{
		int n_read;
		int filefd;
		struct Msg getmsg;//服务器发送到客户端的结构体消息
		char *filename;
		
		//getmsg.secondBuf = (char *)malloc(1024);
		//memset(getmsg.secondBuf,'\0',1024);
		n_read =read(fd,&getmsg,sizeof(getmsg));//读取服务器向客户端发送数据
		if(n_read == 0){
			printf("sever is quit\n");
			exit(-1);
		}
		if(getmsg.type == DOFILE){//当客户端输入的指令是GET时,需要在客户端创建需要获取的文件
			filename=get_cmd_dir(msg.cmd);//msg.cmd 客户端要发送给服务器的消息结构体
			filefd=open(filename,O_RDWR|O_CREAT,0666);
			write(filefd,getmsg.secondBuf,strlen(getmsg.secondBuf));
			close(filefd);
			fflush(stdout); //调用printf()时,输出的结果一般会被标准库缓存起来,可能不会及时打印写出到输出设备上面,此时就可以用fflush(stdout)强制把缓存内容进行输出
		}
		else{//将需要在服务器执行的指令的运行结果打印
			printf("---------------------\n");
			printf("%s\n",getmsg.data);
			printf("---------------------\n");
		}


}
int main(int argc,char *argv[])
{
	int client_fd;
	int nread;	
	int ret;
	char readBuf[128];
	
	struct sockaddr_in client_addr;
	struct Msg msg;

	if(argc !=3){
		printf("请输入IP地址和端口号\n");
		exit(-1);
	}
	memset(&client_addr,0,sizeof(struct sockaddr));

	client_fd=socket(AF_INET,SOCK_STREAM,0);
	if(client_fd==-1){
		perror("socket");
		exit(-1);
	}

	client_addr.sin_family=AF_INET;
	client_addr.sin_port=htons(atoi(argv[2]));

	inet_aton(argv[1],&(client_addr.sin_addr));


	if(connect(client_fd,(struct sockaddr*)&client_addr,sizeof(struct sockaddr))==-1){

		perror("connect");
		exit(-1);
	}
	printf("connecting.....\n");//连接客户端
	while(1){
		memset(&msg,0,sizeof(msg));
		putchar('>');
		fgets(msg.cmd,1024,stdin);
		if(msg.cmd[strlen(msg.cmd) - 1] == '\n')  {      // 去掉换行符
    		 msg.cmd[strlen(msg.cmd) - 1] = '\0'; }
		//gets(msg.cmd);
		ret=cmd_handler(msg,client_fd);//将获取的指令送入指令处理函数中,并将返回的值判断
		if(ret>IFGO){//如果输入的指令像ls,pwd等需要在服务器完成处理的则进行下面的操作,而像lls这样在客户端
			continue;//完成的指令不需要向服务器进行数据交流,完成后直接退出循环进行下一次指令的输入
		}
		if(ret==-1){
			printf("cmd error,please put new cmd\n");
			continue;
		}
		c_msg_handler(msg,client_fd);

	}

	return 0;
}
  • config.h
#define LS 0
#define GET 1
#define PWD 2

#define IFGO 3


#define LCD 4
#define LLS 5
#define CD 6
#define PUT 7
#define QUIT 8
#define DOFILE 9
#define LPWD 10


struct Msg
{	
	int type;
	char cmd[1024];
	char data[1024];
	char secondBuf[1024];
};

五、项目总结(遇到的问题)

1.在客户端进行put操作时,为防止strtok将字符串破坏,导致无法获取put的目标文件,需要在引入一个字符串的变量保存。

strcpy(cmdTemp,msg.cmd);//防止strok破坏msg.cmd
dir=get_cmd_dir(cmdTemp);

2.在进行对输入字符串进行检测的时候,lcd的检测一定要放在cd的前面,因为cdlcd中的一个子字符串,所以如果cd的检测是放在lcd的前面的话,那么无论是输入的是cd还是lcd,都会是返回cd

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-11-05 00:57:57  更:2022-11-05 01:00:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 3:00:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计