【Linux C】虚拟机Ubuntu之FTP云盘项目(含源码解析)

Linux C 之FTP云盘项目(网络编程)

一、项目简介

1.引言

本项目为本人初次入坑linux c 网络编程,该项目比较基础,还有待优化,此外,还存在一个bug:目前的文件传输大小受限于字符数组的初值,未能实现大量拷贝;但五脏俱全,该有的基本功能还是有的。

如图:data用来存放命令,secondBuf用来存放文件内容

在这里插入图片描述

2.项目概况

本项目是在ubuntu-12.04.5-64的linux操作系统下运行,c语言架构,客户端和服务器直接通过网络进行交互,采用的是面向连接TCP传输协议,实现客户端远程获取服务器磁盘上的文件内容,同时也能将客户端本地文件进行上传,完成下载文件和文件上传的功能

功能实现如下:

C语言编程,使用socket进行通信,采用TCP协议;服务器端先调用socket()函数初始化网络服务,再使用bind()函数进行信息绑定,如ip地址等;接着启动listen()函数监听,当accept()函数接收到成功连接(完成TCP三次握手)的客户端,每次有一个新的客户端成功连接,服务器就会调用fork()函数创建一个新的子进程进行对接,提供相应服务;服务器会通过getDesDir()字符串处理函数分割客户端指令,进行不同指令的处理;文件下载则是利用文件编程操作获取服务器磁盘文件上的内容,并将内容通过网络发送给客户端,客户端收到数据后创建同名新文件并将数据写入,文件上传也是同理;普通的文件夹操作指令如ls,pwd等,则是通过popen(),system()等函数实现


二、项目源码(含部分解析)

1.服务器端server.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
//#include <Msgconfig.h>
#include <sys/stat.h>
#include <fcntl.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

struct Msg{
	
	int type;
	char data[128];
	char secondBuf[65535];
	//msgFileBuf本来想用来传输大量文件信息的,没搞通,暂时不用
	char *msgFileBuf;
};

//获取命令比较数ret
int get_cmd_type(char *cmd){
	
	//处理不带参数命令
	//strcmp:第一个字符串等于第二个字符串,则返回0
	if(!strcmp("ls",  cmd))           return LS;
	if(!strcmp("quit",cmd))           return QUIT;
	if(!strcmp("pwd", cmd))           return PWD;
	
	//处理带参数命令
	//strstr:返回值为char * 类型(返回指向str1中第一次出现的str2的指针);
	//如果str2不是str1的一部分,则返回空指针
	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;
	//strtok:该函数返回被分解的第一个子字符串指针;若无可检索的字符串,则返回空指针
	//而第二次调用strtok的时候,传入的参数应该为NULL
	//使得该函数默认使用上一次未分割完的字符串继续分割
	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);
	//自写函数get_cmd_type(),把不同命令对应到不同的整型数并返回比较数ret,方便后续switch分支
	int ret = get_cmd_type(msg.data);
	
	switch(ret){
		case LS:
		case PWD:
			msg.type = 0;
			FILE *r;
			//popen:获取命令运行的输出结果
			r = popen(msg.data,"r");
			//fread:从文件中读取若干字节数据到内存缓冲区中
			fread(msg.data,sizeof(msg.data),1,r);
			//write:将缓冲区内容通过msg结构体发送给客户端
			write(fd,&msg,sizeof(msg));
			break;
		case CD:
			msg.type = 1;
			//自写函数getDesDir(),分割字符串,获取cd命令后面的参数
			char *dir = getDesDir(msg.data);
			printf("dir:%s\n",dir);
			//chdir函数用于改变当前工作目录,调用参数是指向目录的指针
			chdir(dir);
			break;
		case GET:
			file = getDesDir(msg.data);
			
			//access(file,F_OK):判断当前文件是否存在
			if(access(file,F_OK) == -1){
				strcpy(msg.data,"No This File!");
				write(fd,&msg,sizeof(msg));
			}
			else{
				msg.type = DOFILE;  //一个标志,提醒客户端有文件可读
				
				//打开文件并读取到dataBuf缓冲区
				//fdfile 文件操作标识符
				fdfile = open(file,O_RDWR);
				read(fdfile,dataBuf,sizeof(dataBuf));
				close(fdfile);
				
				strcpy(msg.data,dataBuf);
				//将msg消息结构体发送给客户端
				write(fd,&msg,sizeof(msg));
				break;
			}
		case PUT:
			//在服务端创建客户端同名文件
			fdfile = open(getDesDir(msg.data),O_RDWR|O_CREAT,0666);
			//写入客户端文件数据,完成文件上传拷贝
			write(fdfile,msg.fileBuf,strlen(msg.fileBuf));
			//printf("msg.fileBuf:%s\n",msg.fileBuf);
			close(fdfile);
			break;
		case QUIT:
			printf("client quit!\n");
			exit(-1);
	}
	
}


int main(int argc, char **argv){
	//argc -- 命令行参数的总个数,包括 可执行程序名。
	//argv[i] -- 第 i 个参数
	//argv[0] -- 可执行程序名

	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;
	
	//argc -- 命令行参数的总个数,包括 可执行程序名
	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   初始化网络服务
	//AF_INET 表示使用互联网协议族   
	//SOCK_STREAM 指定socket类型 流式套接字 TCP协议   0:自适应选择对应的传输协议
	//s_fd 网络操作标识符,类似文件操作
	s_fd = socket(AF_INET,SOCK_STREAM,0);
	if(s_fd == -1){   
		//启动失败 返回-1 打印错误 结束任务
		perror("socket");
		exit(-1);
	}
	s_addr.sin_family = AF_INET;
	//字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。
	//htons:返回端口网络字节序的值  (网络字节序=大端字节序:将高序字节存储在起始地址)
	//如:0x01020304  大端字节序--> 0000 0001 放在首地址位置(底部)
	//atoi: 字符串 转 整型
	//argv[i] -- 第 i 个参数
	//argv[0] -- 可执行程序名
	s_addr.sin_port = htons(atoi(argv[2]));
	//inet_aton:把字符串形式的“192.168.1.123”转为网络能识别的格式
	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){
		//accept会一直阻塞,当出现完成TCP三次握手的客户端时,才往下走
		//c_fd  成功连接后的网络操作标识符
		c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
		if(c_fd == -1){
			perror("connect");
		}
		//inet_ntoa:把网络格式的ip地址转为字符串形式
		printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
		
		//每次有一个新的客户端成功连接,fork()就创建一个新的子进程来对接相应客户端进行服务
		//fork()返回进程的标识符,0代表是子进程
		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("clinet out\n");
					break;
				}
				else if(n_read > 0){
						//如果消息读取成功,执行消息回调函数来处理不同的消息
						msg_handler(msg,c_fd);
				}
			}
		}
	}
	close(c_fd);
	close(s_fd);
	return 0;
}

2.客户端client.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
//#include <Msgconfig.h>
#include <sys/stat.h>
#include <fcntl.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


struct Msg{
	
	int type;
	char data[128];
	char secondBuf[65535];
	//msgFileBuf本来想用来传输大量文件信息的,没搞通,暂时不用
	char *msgFileBuf;
	
};

//获取命令后面的参数
char * getdir(char *cmd){
	
	char *p;
	//strtok:该函数返回被分解的第一个子字符串指针;若无可检索的字符串,则返回空指针
	//而第二次调用strtok的时候,传入的参数应该为NULL
	//使得该函数默认使用上一次未分割完的字符串继续分割
	p = strtok(cmd," ");
	p = strtok(NULL," ");
	return p;
}

//获取命令比较数ret
int get_cmd_type(char *cmd){
	
	//处理不带参数命令
	//strcmp:第一个字符串等于第二个字符串,则返回0
	if(!strcmp("lls",  cmd))          return LLS;
	if(!strcmp("ls",  cmd))           return LS;
	if(!strcmp("quit",cmd))           return QUIT;
	if(!strcmp("pwd", cmd))           return PWD;
	
	//处理带参数命令
	//strstr:返回值为char * 类型(返回指向str1中第一次出现的str2的指针);
	//如果str2不是str1的一部分,则返回空指针
	if(strstr(cmd,"lcd") != NULL)     return LCD;
	if(strstr(cmd,"cd")  != NULL)     return CD;
	if(strstr(cmd,"get") != NULL)     return GET;
	if(strstr(cmd,"put") != NULL)     return PUT;
	
	return -1;
}


int cmd_handler(struct Msg msg, int fd){
	
	char *dir = NULL;
	char buf[32];
	int ret;
	int fdfile;
	int size;
	
	//自写函数get_cmd_type(),把不同命令对应到不同的整型数并返回比较数ret,方便后续switch分支
	ret = get_cmd_type(msg.data);
	
	switch(ret){
		case LS:
		case CD:
		case PWD:
			msg.type = 0;
			//write:将缓冲区内容通过msg结构体发送给服务器端
			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);
			//access(file,F_OK):判断当前文件是否存在
			if(access(dir,F_OK) == -1){
				printf("%s not exsit\n",dir);
			}
			else{	
				//打开文件并读取到dataBuf缓冲区
				//fdfile 文件操作标识符
				fdfile = open(dir,O_RDWR);
				read(fdfile,msg.secondBuf,sizeof(msg.secondBuf));
				close(fdfile);
				//将msg消息结构体发送给服务器端
				write(fd,&msg,sizeof(msg));
			}
			break;
		case LLS:
			system("ls");
			break;
		case LCD:
			dir = getdir(msg.data);
			chdir(dir);
			break;
		case QUIT:
			printf("client quit!\n");
			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));
	
	
	//阻塞读取,读取为0,说明连接已丢失
	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");
		printf(">");
		//fflush(stdout):清空输出缓冲区
		fflush(stdout);
	}
	
}

int main(int argc, char **argv){
	//argc -- 命令行参数的总个数,包括 可执行程序名。
	//argv[i] -- 第 i 个参数
	//argv[0] -- 可执行程序名

	int c_fd;
	
	struct sockaddr_in c_addr;
	struct Msg msg;
	
	//argc -- 命令行参数的总个数,包括 可执行程序名
	if(argc != 3){
		printf("param is not good\n");
		exit(-1);
	}
	
	memset(&c_addr,0,sizeof(struct sockaddr_in));

	//1.socket   初始化网络服务
	//AF_INET 表示使用互联网协议族   
	//SOCK_STREAM 指定socket类型 流式套接字 TCP协议   0:自适应选择对应的传输协议
	//s_fd 网络操作标识符,类似文件操作
	c_fd = socket(AF_INET,SOCK_STREAM,0);
	if(c_fd == -1){   
		//启动失败 返回-1 打印错误 结束任务
		perror("socket");
		exit(-1);
	}
	c_addr.sin_family = AF_INET;
	//字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。
	//htons:返回端口网络字节序的值  (网络字节序=大端字节序:将高序字节存储在起始地址)
	//如:0x01020304  大端字节序--> 0000 0001 放在首地址位置(底部)
	//atoi: 字符串 转 整型
	//argv[i] -- 第 i 个参数
	//argv[0] -- 可执行程序名
	c_addr.sin_port = htons(atoi(argv[2]));
	//inet_aton:把字符串形式的“192.168.1.123”转为网络能识别的格式
	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){
		//msg.data消息缓冲区清零
		memset(msg.data,0,sizeof(msg.data));
		//“>”   输入美观化
		if(mark == 0)   printf(">");
		//gets:获取用户输入并存放到消息缓冲区
		gets(msg.data);
		
		//****输入美观化****
		if(strlen(msg.data) == 0){
			if(mark == 1){
				printf(">");
			}
			continue;
		}
		mark = 1;
		//******************
		
		//自写函数cmd_handler(),对用户输入的命令进行处理
		//给服务器发送相应命令
		int ret = cmd_handler(msg,c_fd);
		
		//判断哪些指令还需获取等待服务器返回
		if(ret > IFGO){
			putchar('>');
			//fflush(stdout):清空输出缓冲区
			fflush(stdout);
			continue;  //进入新一轮循环
		}
		if(ret == -1){
			printf("command not \n");
			printf(">");
			//fflush(stdout):清空输出缓冲区
			fflush(stdout);
			continue;
		}
		//获取服务器的返回内容
		handler_server_message(c_fd,msg);
	}
	

	return 0;
}

三、效果展示

本次效果展示的客户端和服务器均在同一台虚拟机上(局域网下即可),但两者运行在不同的文件夹下

FTP文件夹下运行服务器端(左边),FTP2文件夹下运行客户端(右边)

如图,运行前:

在这里插入图片描述

开始运行,连接成功:

在这里插入图片描述

尝试命令ls,lls:

在这里插入图片描述

客户端上传文件3.txt给服务器:

在这里插入图片描述

客户端从服务器端获取文件1.txt:

在这里插入图片描述

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: Linux C FTP是一种在Linux操作系统下使用C语言开发的FTP(File Transfer Protocol,文件传输协议)客户端程序。它可以通过FTP协议在网络上进行文件的上传和下载。 Linux C FTP实现主要涉及以下几个方面:socket编程、命令解析和响应处理、文件传输。 首先,Linux C FTP利用socket编程来建立与FTP服务器的连接,通过socket套接字进行数据的传输和通信。 其次,命令解析和响应处理是FTP客户端程序的关键。用户可以通过输入指令来进行各种操作,如登录、查看文件列表、上传和下载文件等。Linux C FTP通过解析用户的输入指令,并根据指令向FTP服务器发送请求。同时,它还能够接收FTP服务器返回的响应信息,并进行相应的处理和显示。 最后,文件传输Linux C FTP的核心功能之一。通过FTP协议,可以实现文件在客户端和服务器之间的相互传输。用户可以选择上传文件到服务器或从服务器下载文件到本地。在文件传输过程Linux C FTP需要处理数据的分块、校验和传输断等问题,以保证文件的完整性和可靠性。 总结来说,Linux C FTP是一种基于Linux操作系统的FTP客户端程序,通过socket编程实现FTP服务器的连接和通信,通过命令解析和响应处理实现用户指令的解析和处理,通过文件传输实现文件的上传和下载。它为用户提供了方便快捷的文件传输功能,是网络传输文件的重要工具之一。 ### 回答2: Linux C FTP是指在Linux系统下使用C语言编写的FTP(File Transfer Protocol,文件传输协议)客户端。FTP是一种用于在网络传输文件的协议,它允许用户通过客户端与远程服务器进行文件的上传、下载和管理。 使用C语言编写Linux FTP客户端,可以通过套接字(Socket)来实现与服务器的通信。通过建立与远程服务器的连接,并发送相应的FTP命令来实现文件传输功能。 首先,客户端需要通过socket函数创建一个套接字,并指定远程服务器的IP地址和FTP服务器的端口号。然后,使用connect函数将套接字连接到服务器。 连接建立后,客户端可以使用send函数向服务器发送FTP命令,例如发送"USER"命令进行登录验证,发送"PASS"命令输入登录密码。利用recv函数接收服务器返回的响应信息,判断登录是否成功。 登录成功后,客户端可以发送"RETR"命令下载文件,指定要下载的文件名和保存的本地路径;发送"STOR"命令上传文件,指定要上传的本地文件名和服务器保存的路径。客户端还可以发送"LIST"命令获取服务器上的文件列表。 发送完FTP命令后,客户端通过recv函数接收服务器返回的响应信息,根据返回的响应状态码进行相应的处理。例如,状态码为"150"表示服务器准备传输文件,客户端可以通过recv函数接收数据并将数据写入指定的本地文件。 当文件传输结束后,客户端可以使用"QUIT"命令关闭连接并退出。 以上是简单介绍了使用Linux C语言编写FTP客户端的基本步骤。在实际应用,还需要处理其他一些细节,比如错误处理、断点续传等,以提高文件传输的可靠性和效率。 ### 回答3: Linux C FTP是指在Linux系统下使用C语言编写的FTP客户端程序。FTP全称为File Transfer Protocol,是一种用于在网络上进行文件传输的标准协议。利用FTP,用户可以通过一个客户端程序连接到FTP服务器,进行上传和下载文件的操作。 Linux C FTP程序的编写需要使用C语言Linux系统提供的相关库函数。首先,需要建立一个与FTP服务器的连接,可以使用socket函数创建一个套接字,并使用connect函数将套接字连接到服务器的IP地址和端口号。连接建立后,客户端需要与服务器进行握手和认证,通常通过用户输入用户名和密码进行身份验证。 在认证成功后,客户端可以发送FTP命令到服务器,包括获取文件列表、上传文件、下载文件等操作。这些FTP命令通过客户端发送给服务器进行处理。常用的FTP命令包括: - LIST:获取服务器上的文件列表 - RETR:从服务器下载文件 - STOR:上传文件到服务器 - DELE:删除服务器上的文件 - MKD:创建目录 - CWD:切换目录 - PWD:获取当前所在目录 客户端程序需要根据用户的操作,在C语言编写相应的函数来执行对应的FTP命令。例如,下载文件可以使用recv函数接收服务器发来的文件数据,并使用write函数将数据写入到本地文件;上传文件则需要使用send函数将文件数据发送给服务器。 编写Linux C FTP程序,需要熟悉Linux系统的网络编程和FTP协议的相关知识。同时,还需要考虑到网络传输的安全性和稳定性等因素,例如可以使用SSL/TLS来加密数据传输,以及进行错误处理和异常处理,以提高程序的健壮性。 总之,Linux C FTP是一种在Linux系统下使用C语言编写的FTP客户端程序,通过与FTP服务器进行交互,实现文件的传输和管理。编写这样的程序需要了解Linux系统的网络编程和FTP协议的相关知识,并综合考虑程序的安全性和稳定性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

索子也敲代码

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

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

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

打赏作者

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

抵扣说明:

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

余额充值