【Linux篇】项目:FTP服务器

一、项目概述

1.该项目是基于Linux网络编程实现的FTP服务器,服务器由服务端和客户端组成,具有浏览远程服务端的文件和浏览客户端本地文件,同时支持客户端对远程服务端文件的上传和下载。

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

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

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

二、编程实现

源码:https://gitee.com/GeekerGao/ftp-network-disk

config.h

// 定义常量
#define LS   0   // 常量 LS,表示列出目录内容
#define GET  1   // 常量 GET,表示从服务端获取文件到客户端
#define PWD  2   // 常量 PWD,表示显示当前工作目录

#define IFGO 3   // 常量 IFGO,用于判断命令是否需要进一步处理

#define LCD  4   // 常量 LCD,表示切换本地工作目录
#define LLS  5   // 常量 LLS,表示在本地执行 ls 命令
#define CD   6   // 常量 CD,表示切换服务器工作目录
#define PUT  7   // 常量 PUT,表示从客户端上传文件到服务端

#define QUIT   8   // 常量 QUIT,表示退出客户端或服务器
#define DOFILE 9   // 常量 DOFILE,表示传输文件内容

// 定义消息结构体 Msg
struct Msg
{
    int type;                 // 消息类型,表示不同的命令或操作
    char data[1024];          // 存储消息数据,如命令、消息内容等
    char secondBuf[128];      // 存储消息中的第二部分数据,如文件内容等
};

服务端

server.c

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


int get_cmd_type(char *cmd) // 根据命令字符串获取命令类型的整数值
{
	if(!strcmp("ls",cmd))         return LS;
	if(!strcmp("quit",cmd))       return QUIT;
	if(!strcmp("pwd",cmd))        return PWD;
	if(strstr(cmd,"cd")!=NULL)    return CD;
	if(strstr(cmd,"get")!=NULL)   return GET;
	if(strstr(cmd,"put")!=NULL)   return PUT;

	return 100;
}

char *getDesDir(char *cmsg)// 获取目标目录路径
{
	char *p;
	p = strtok(cmsg," ");
	p = strtok(NULL," ");
	return p;
}


void msg_handler(struct Msg msg, int fd)// 处理客户端消息
{
	char dataBuf[1024]={0};// 用于存储文件内容的缓冲区
	char *file = NULL;// 用于存储要操作的文件名
	int fdfile;
	
	
	printf("cmd:%s\n",msg.data);// 打印接收到的命令
	int ret = get_cmd_type(msg.data);// 获取命令类型的整数值
	
	switch(ret){
		case LS:
		case PWD:
			msg.type = 0;// 设置消息类型,用于不同的响应
			FILE *r = popen(msg.data,"r");// 执行 shell 命令并创建子进程
			fread(msg.data,sizeof(msg.data),1,r);// 从子进程读取数据到缓冲区
			write(fd, &msg,sizeof(msg)); // 发送数据给客户端
			break;
		case CD:
			msg.type = 1;
			char *dir = getDesDir(msg.data);// 获取要切换的目标目录
			printf("dir:%s\n",dir);
			chdir(dir);// 切换当前工作目录
			break;
		case GET:
			file = getDesDir(msg.data);// 获取要下载的文件名
			
			if(access(file,F_OK) == -1){ //判断文件是否存在
				strcpy(msg.data,"No This File!");
				write(fd,&msg,sizeof(msg));// 发送消息给客户端
			}else{
				msg.type = DOFILE;// 设置消息类型为发送文件内容
				
				fdfile = open(file,O_RDWR);// 打开文件以供读取
				read(fdfile,dataBuf,sizeof(dataBuf));// 读取文件内容到缓冲区
				close(fdfile);//关闭文件

				strcpy(msg.data,dataBuf);// 将文件内容复制到消息数据中
				write(fd,&msg,sizeof(msg));// 向客户端发送包含文件内容的消息
			}
			break;
		case PUT:
			fdfile = open(getDesDir(msg.data),O_RDWR|O_CREAT,0666);//打开/创建文件
			write(fdfile, msg.secondBuf, strlen(msg.secondBuf));// 将客户端发送的文件数据写入文件
			close(fdfile);//关闭文件
			break;
		case QUIT:
			printf("client quit!\n");// 打印客户端退出信息
			exit(-1);// 退出程序
	}
}


int main(int argc, char **argv)
{
	int s_fd;
	int c_fd;
	int n_read;
	char readBuf[128];

	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;
	struct Msg msg;

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

	memset(&s_addr,0,sizeof(struct sockaddr_in));
	memset(&c_addr,0,sizeof(struct sockaddr_in));//数据清空

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

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


	//2. bind
	bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));

	//3. listen
	listen(s_fd,10);
	//4. accept
	int clen = sizeof(struct sockaddr_in);
	while(1){
		c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
		if(c_fd == -1){
			perror("accept");
		}

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

		if(fork() == 0){ // 创建子进程处理客户端连接
			while(1){
				memset(msg.data,0,sizeof(msg.data));//清除数据
				n_read = read(c_fd, &msg, sizeof(msg));	// 从客户端读取消息
				if(n_read == 0){ //判断读取的字节数
					printf("client out\n");// 打印客户端断开连接的信息
					break;
				}else if(n_read > 0){
					msg_handler(msg,c_fd);// 处理客户端消息
				}
			}
		}

	}
	close(c_fd);//关闭客户端socket
	close(s_fd);//关闭服务端socket
	return 0;
}

客户端

client.c

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


char * getdir(char *cmd)//从命令字符串中解析出参数部分,用于获取操作命令所涉及的文件路径
{
	char *p;

	p = strtok(cmd, " " );
	p = strtok(NULL, " ");

	return p;
}

int get_cmd_type(char *cmd)//根据输入的命令字符串判断命令类型,返回相应的整数标识
{
	if(strstr(cmd,"lcd"))   return LCD;


	if(!strcmp("quit",cmd))   return QUIT;
	if(!strcmp("ls",cmd))    return LS;
	if(!strcmp("lls",cmd))   return LLS;
	if(!strcmp("pwd",cmd))   return LS;

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

	return -1;
}

int cmd_handler(struct Msg msg, int fd)
{
	char *dir = NULL;
	char buf[32];
	int ret;
	int filefd;

	ret = get_cmd_type(msg.data);// 根据命令类型获取对应的整数标识

	switch(ret){
		case LS:
		case CD:
		case PWD:
			msg.type = 0;
			write(fd,&msg,sizeof(msg));// 向服务器发送消息,请求执行列目录、切换目录、显示当前目录等操作
			break;
		case GET:
			msg.type = 2;
			write(fd,&msg,sizeof(msg));//向服务器发送消息,请求获取文件
			break;
		case PUT:
			strcpy(buf,msg.data);
			dir = getdir(buf);// 获取上传文件的路径

			if(access(dir,F_OK) == -1){ //判读要上传的文件是否存在
				printf("%s not exsit\n",dir);
			}else{	
				filefd = open(dir,O_RDWR);//打开文件
				read(filefd,msg.secondBuf,sizeof(msg.secondBuf)); // 读取文件内容到消息结构的缓冲区
				close(filefd);//关闭文件

				write(fd,&msg,sizeof(msg));// 将消息发送给服务器,执行上传文件操作
			}
			break;
		case LLS:
			system("ls");// 在客户端执行系统命令,显示本地目录内容
			break;
		case LCD:
			dir = getdir(msg.data);// 获取切换目录的路径
			chdir(dir);// 切换本地目录
			break;
		case QUIT:
			strcpy(msg.data,"quit");
			write(fd,&msg,sizeof(msg));// 向服务器发送退出消息
			close(fd);// 关闭连接
			exit(-1);

	}
	return ret;
}


void handler_server_message(int c_fd, struct Msg msg)//处理从服务器接收到的消息
{
	int n_read;
	struct Msg msgget;
	int newfilefd;

	n_read = read(c_fd, &msgget, sizeof(msgget));// 从服务器读取消息响应

	if(n_read == 0){
		printf("server is out,quit\n");// 如果没有读取到数据,服务器可能已关闭
		exit(-1);
	}

	else if(msgget.type == DOFILE){

		char *p = getdir(msg.data);// 获取要保存文件的路径
		newfilefd = open(p,O_RDWR|O_CREAT,0600);// 创建本地文件	
		write(newfilefd,msgget.data,strlen(msgget.data));// 将服务器传来文件数据写本地文件
		putchar('>');// 输出命令提示符
		fflush(stdout);
	}
	else{
		printf("--------------------------------\n");
		printf("\n%s\n",msgget.data);// 打印服务器返回的数据,可能是目录列表等信息
		printf("--------------------------------\n");

		putchar('>');// 输出命令提示符
		fflush(stdout);
	}

}

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

	struct Msg msg;

	memset(&c_addr,0,sizeof(struct sockaddr_in));

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

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

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

	//2.connect	
	if(connect(c_fd, (struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
		perror("connect");
		exit(-1);
	}
	printf("connect ...\n");
	int mark = 0;// 标记是否需要输出命令提示符	
	while(1){
		memset(msg.data,0,sizeof(msg.data));// 清除消息缓冲区
		if(mark == 0)  printf(">"); // 输出命令提示符

		gets(msg.data);// 获取用户输入的命令

		if(strlen(msg.data) == 0){
			if(mark == 1){
				printf(">");
			}
			continue;// 用户未输入命令,继续循环
		}

		mark = 1;// 标记已经有输入的命令

		int ret = cmd_handler(msg,c_fd);// 处理用户输入的命令

		if(ret>IFGO){
			putchar('>');
			fflush(stdout);
			continue; // 如果命令需要进一步处理,继续循环
		}
		if(ret==-1){
			printf("command not \n"); // 无法识别的命令
			printf(">");
			fflush(stdout);
			continue;// 继续循环等待下一个命令
		}
		handler_server_message(c_fd, msg);// 处理服务器响应的消息
	}
	return 0;
}

三、项目实现

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序猿gao

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

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

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

打赏作者

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

抵扣说明:

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

余额充值