首页 > 其他分享 >FTP网盘

FTP网盘

时间:2024-10-30 16:46:50浏览次数:3  
标签:FTP 文件 int 网盘 cmd char msg 客户端

1. 项目实现的功能

1.1. 在客户端对服务器的操作:
  • 获取服务器的文件(get)
  • 查看服务器当前路径下的所有文件(ls)
  • 进入服务器的某个文件夹(cd)
  • 上传文件到服务器(put)
  • 查看当前服务器路径(pwd)
  • 退出(quit)
1.2. 在客户端本地的功能实现:
  • 查看客户端当前路径下有哪些文件(lls)
  • 进入客户端的某个文件夹(lcd)
  • 查看当前客户端路径(lpwd)
  • 退出连接(quit)

2. 项目实现思路

2.1. socket服务器和客户端开发步骤:
  • 服务器server:
  • 创建套接字(socket)
  • 将socket与IP地址和端口绑定(bind)
  • 监听被绑定的端口(listen)
  • 接收连接请求(accept)
  • 当有客户端接入时创建子进程对接并处理客户端发来的请求
  • 客户端client:
  • 创建套接字(socket)
  • 连接指定计算机的端口(connect)
  • 接收输入向socket中写入信息(write)
  • 获取客户端发送的内容像ls,pwd,get指令,需要进行(read)
2.2. 指令的实现思路:
  • ls与pwd:调用popen函数,执行命令,并获取执行命令后的输出内容,将内容发回到客户端
  • cd:将cd指令发送到服务器,在服务器端调用chdir实现路径的切换
  • get:客户端获取服务器的某个文件,服务器首先通过access函数判断文件是否存在,存在则将文件的内容发送到客户端,客户端创建文件,并将服务器发送的内容保存至文件中,实现get指令。
  • put:客户端向服务器发送文件,客户端首先通过access函数判断文件是否存在,存在则将文件的内容发送到服务器,服务器创建文件,并将客户端发送的内容保存至文件中,实现put指令。
  • lls,lpwd:调用system函数即可。
  • lcd:在客户端直接调用chdir函数即可

3. 项目用到的函数

3.1. 判断文件是否存在函数access()原型和头文件:
/*
	Linux下 man 2 access查看手册
*/
#include <unistd.h>
 
int access(const char *pathname, int mode);
 
int 	函数返回值,返回值如果成功(所有请求的权限都被授予,或者mode为F_OK并且文件存在),返回0。如果出现错误(mode中至少有一个		  位请求的权限被拒绝,或者mode为F_OK而文件不存在,或者发生了其他错误),则返回-1,并适当设置errno。
 
char *pathname	需要检测的文件路径名
    
int mode		需要测试的操作模式
1. R_OK 测试读许可权
2. W_OK 测试写许可权
3. X_OK 测试执行许可权
4. F_OK 测试文件是否存在
    
/*函数说明:判断参数中的文件名是否存在*/
3.2. 字符串输入函数fgets()原型和头文件:
/*
	Linux下 man fgets查看手册
*/
#include <stdio.h>
 
char *fgets(char *s, int size, FILE *stream);
 
char *			函数返回值,fgets()成功时返回s(数组首地址),错误时返回NULL,或者在没有读取任何字符的情况下出现文件结束。
 
char *s			它是一个指向字符数组的指针,用于存储读取的文本行。
int size		表示要读取的最大字符数(包括空字符)
FILE *stream	表示从何种流中读取,可以是标准输入流 stdin,也可以是文件流,即从某个文件中读取
    
/*函数说明:
    虽然用 gets() 时有空格也可以直接输入,但是 gets() 有一个非常大的缺陷,即它不检查预留存储区是否能够容纳实际输入的数据,换句	 话说,如果输入的字符数目大于数组的长度,gets 无法检测到这个问题,就会发生内存越界,所以编程时建议使用 fgets()
*/
3.3. 字符串分割函数strtok()原型和头文件:
/*
	Linux下 man strtok查看手册
*/
#include <string.h>
 
char *strtok(char *str, const char *delim);	
 
char *		会返回一个指向被分割出的第一个子字符串(即第一个空格之前的部分)的指针。如果没有更多的分割部分,则返回 NULL。
    
char *str	指向要被分割的字符串的指针
    		最初调用时,为需要分割的字符串。后续调用时,为 NULL 以获取下一部分标记。
    
char *delim	指定用来分割字符串的分隔符,比如说空格" "
    		一个包含分隔符的字符串,这些字符用于分割字符串中的标记。
    
/*函数说明:strtok 函数用于将字符串 str 分割成一系列标记 (token),这些标记由参数 delim 中的字符分隔开来。*/
    
使用方式:
strtok 首次调用时传入需要分割的字符串,并传入分隔符字符串。后续调用时,将 str 参数传递为 NULL,以继续检索同一字符串中的下一部分内容
char str[] = "hello world";
char delim[] = " ";
char *token = strtok(str, delim);
 
while (token != NULL) {
    printf("Token: %s\n", token);
    token = strtok(NULL, delim);
}
3.4. 当前工作目录更改为指定的工作目录函数chdir()原型和头文件:
/*
	Linux下 man 2 chdir查看手册
*/
#include <unistd.h>
 
int chdir(const char *path);
 
int			函数返回值,通常,当成功时返回 0,失败时返回 -1,并设置 errno 来指示出错原因。
    
char *path	参数是一个字符串指针 path,表示要改变到的目录路径。该路径可以是绝对路径或相对路径。
    
/*函数说明:
	是一个标准库函数,它的主要功能是改变当前工作目录。它接收一个路径参数并尝试将当前工作目录更改为指定的路径,在成功时返回 0,失败时返回 -1 并设置错误代码。这个功能对于实现像 FTP 服务器这样的程序非常重要,因为它允许服务器根据客户端请求动态更改当前工作目录。
*/

4. FTP项目实现

  • 客户端
#include <stdio.h>               // 包含标准输入输出头文件
#include <stdlib.h>              // 包含标准库函数头文件
#include <sys/types.h>           // 包含数据类型头文件
#include <sys/socket.h>          // 包含Socket通信头文件
#include <netinet/in.h>          // 包含Internet地址族头文件
#include <arpa/inet.h>           // 包含IP地址转换功能头文件
#include <string.h>              // 包含字符串操作函数头文件
#include <unistd.h>              // 包含unistd.h头文件
#include <sys/stat.h>            // 包含文件状态头文件
#include <fcntl.h>               // 包含文件控制头文件

#define LS 		0 		//	定义一个宏 LS,其值为 0。
#define PWD 	1 		//	定义一个宏 PWD,其值为 1。
#define GET 	2 		//	定义一个宏 GET,其值为 2。
#define IFGO 	3 		//	定义一个宏 IFGO,其值为 3。
#define CD 		4 		//	定义一个宏 CD,其值为 4。
#define PUT 	5		//	定义一个宏 PUT,其值为 5。
#define LLS 	6		//	定义一个宏 LLS,其值为 6。
#define LCD 	7		//	定义一个宏 LCD,其值为 7。
#define LPWD 	8		//	定义一个宏 LPWD,其值为 8。
#define QUIT 	9		//	定义一个宏 QUIT,其值为 9。
#define DOFILE	10		// 定义一个宏 DOFILE,其值为 10。

struct MSG{
//  定义了一个名为MSG的结构体,其中包含三个成员:

int type;			  //	一个整型变量,用于表示消息类型。

char cmd[1024];	  //	一个长度为 1024 的字符数组,用于存储命令数据。

char secondBuf[1024]; //	一个长度为 1024 的字符数组,用于存储额外的数据。
};

char* get_cmd_dir(char *cmd) { // 从命令中提取目录名的函数
    char *p = NULL;
    p = strtok(cmd, " ");         // 分割命令字符串
	
	/*使用 strtok 函数分割 cmd 字符串,以空格 " " 作为分隔符。
	strtok 函数的第一个调用将返回字符串中第一个分隔符之前的部分,
	之后每次调用 strtok(带有 NULL 作为第一个参数)将返回下一个分割后的子串,直到字符串尾部。
	*/。‘
    p = strtok(NULL, " ");        // 获取目录名
    return p;                   // 返回目录名
}
int cmd_type(char *cmd) {       // 判断命令类型的函数

	// ‘LS‘命令
	if(!strcmp("ls", cmd))  return LS;  // 如果相等  == 0,就返回1(!=) [ls] 因为  strcmp函数,两者作比较 相等返回0 ,不相等返回 -1或 整数
		
	// 条件为真;返回 LS
		
	// 比较 'pwd' 命令
    if(!strcmp("pwd", cmd)) return PWD;   
		
		   	
	// 比较 'quit' 命令
    if(!strcmp("quit", cmd))   return QUIT;   //用于结束客户端与服务器之间的会话        
				    				
	 // 查找 'CD'命令
    if(strstr(cmd, "cd"))   return CD;   // strstr 查找函数    如果 cmd 中包含cd字符,就返回 CD命令          
		
	// 查找 'get' 命令	
    if(strstr(cmd, "get"))  return GET;  // strstr 查找函数    如果 cmd 中包含get字符,就返回get命令;从服务器获取文件并将其传输到客户端
		

	// 查找 'put' 命令	
    if(strstr(cmd, "put")) return PUT; // strstr 查找函数    如果 cmd 中包含put字符,就返回put命令;从客户端传输到服务器
		
	  
    return -1;                          // 未知命令;返回-1
}
int	msg_handler(int c_fd,struct MSG msg)
{ 
	int ret;                             // 命令类型
    int fdfile;                   
	int n_fread;
	int n_write;
	// 文件描述符
    char *dir = NULL;                   // 目录名
    char *file = NULL;                  // 文件名
    char dataBuf[1024] = {0};           // 数据缓冲区

    printf("客户端发送消息:%s\n", msg.cmd); // 打印客户端消息
    ret = cmd_type(msg.cmd);            // 将命令类型转为整型

    switch(ret) {           
	// 根据命令类型处理
        case LS:
        case PWD:
            msg.type = 0;               // 设置消息类型为 0,表示成功
            FILE *fp = popen(msg.cmd, "r"); // 打开命令管道
			
			/*FILE* 作为指针类型,用于操作文件流。通过这个指针,
			你可以使用标准 I/O 函数(如 fopen、fclose、fread、fwrite、fprintf、fscanf 等)来执行文件操作。*/
            n_fread = fread(msg.cmd, sizeof(msg.cmd), 1, fp); // 读取命令输出
			if(n_fread == -1){
				printf("读取失败\n");
			}
            n_write = write(c_fd, &msg, sizeof(msg)); // 发送命令执行结果
			if(n_write == -1){
				printf("发送失败\n");
			}
            pclose(fp);                  // 关闭命令管道
            break;
			
        case CD:
            msg.type = 1;               // 设置消息类型为 1
            dir = get_cmd_dir(msg.cmd); // 获取目录名
            
			if (chdir(dir) != 0) {     // 切换到的目标目录的路径。
				printf("切换目录失败\n");
				perror("chdir failed:");
					
			}			
            break;
			
        case GET:
            file = get_cmd_dir(msg.cmd); // 获取文件名
            if(access(file, F_OK) == -1) { // 检查文件是否存在
				//access 是一个 POSIX 标准的 C 函数,用于检查调用进程是否可以访问指定的文件。
				int access(const char *pathname, int mode);
				/*:

				pathname: 指向包含文件路径的字符串。
				mode: 指定访问方式,可以是以下标志的组合:
				F_OK:仅仅检查文件是否存在。
				R_OK:检查文件是否可读。
				W_OK:检查文件是否可写。
				X_OK:检查文件是否可执行。
				返回值:

				如果函数成功,并且指定的文件可以被访问,则返回 0。
				如果函数成功,但文件不可访问,则返回 -1,并设置 errno 以指示错误类型
				*/
				
                strcpy(msg.cmd, "文件不存在!"); // 设置错误信息 strcpy拷贝函数
				n_write = write(c_fd, &msg, sizeof(msg)); // 发送错误信息
				if(n_write == -1){
					printf("get发送失败\n");
				}
            } else {
                msg.type = DOFILE;       // 设置消息类型为 DOFILE == 10
                fdfile = open(file, O_RDWR); // 打开文件
                n_fread = read(fdfile, dataBuf, sizeof(dataBuf)); // 读取文件内容
				
				if(n_fread == -1){
					printf("get读取失败\n");
				}
                close(fdfile);           // 关闭文件
                strcpy(msg.secondBuf, dataBuf); // 将内容存入消息结构体
                n_write = write(c_fd, &msg, sizeof(msg));  // 发送文件内容
				if(n_write == -1){
					printf("get发送失败\n");
				}
            }
            break;
        case PUT:
            fdfile = open(get_cmd_dir(msg.cmd), O_RDWR | O_CREAT, 0666); // 创建并打开文件
            n_write = write(fdfile, msg.secondBuf, sizeof(msg.secondBuf)); // 将内容写入新文件
			
			if(n_write == -1){
					printf("put写入失败\n");
				}
            close(fdfile);          // 关闭文件
            break;
        case QUIT:
            printf("客户端断开连接!\n"); // 打印断开连接消息
            exit(-1);                 // 退出程序
    }
}
int main(int argc ,char **argv)
{
	int c_fd;
	int s_fd;
    int n_read;                       // 读取字节数
	struct MSG msg;                  // 消息结构体
	
	pid_t pid;                        // 子进程 ID

   
	struct sockaddr_in s_addr;  // 服务器地址结构体
    struct sockaddr_in c_addr;  // 客户端地址结构体

    memset(&s_addr, 0, sizeof(s_addr)); // 清零服务器地址
    memset(&c_addr, 0, sizeof(c_addr)); // 清零客户端地址
	 
	//检测ip+ 端口号
	if(argc != 3) {                  // 检查参数个数
        printf("参数错误!请按照格式输入:./serverFTP IP地址 端口号");
        exit(-1);                   // 参数错误,程序退出
    }

		// 创建套接字
	s_fd = socket(AF_INET, SOCK_STREAM, 0); 
    if(s_fd == -1) {
        printf("创建socket失败!");
        exit(-1);
    }
		//设置IP
	s_addr.sin_family = AF_INET; // 设置地址族为 AF_INET(IPv4)
    s_addr.sin_port = htons(atoi(argv[2])); // 设置端口号    将端口号转换成网络字节序
    inet_aton(argv[1], &s_addr.sin_addr); // 设置 IP 地址  IP地址转换成网络格式函数

	// 绑定ip
    int ret = bind(s_fd, (struct sockaddr *)&s_addr, sizeof(s_addr)); // 绑定套接字
    if(ret == -1) {
        printf("绑定socket失败!");
        exit(-1);
    }
	
	// 开始监听连接  
    ret = listen(s_fd, 10); 
    if(ret == -1) {
        printf("监听失败!\n");
    }
    printf("服务器启动成功!\n");

	// 客户端地址长度
    int c_addr_len = sizeof(c_addr); 
		
    while(1) {
		// 接受连接请求
        c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &c_addr_len); 
        if(c_fd == -1) {
            printf("接受客户端连接请求失败!");
            exit(-1);
        }
        printf("客户端连接成功!\n");
        printf("客户端地址:%s:%d\n", inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port)); // 打印客户端地址   网络格式的IP地址转换成字符串    将网络格式的端口号转换成字符串

        pid = fork(); // 创建子进程
        if(pid == -1) {
            printf("创建子进程失败!");
            exit(-1);
        } else if(pid == 0) { // 子进程
            while(1) {
                memset(&msg, 0, sizeof(msg)); // 清零消息结构体
                n_read = read(c_fd, &msg, sizeof(msg)); // 读取客户端消息
                if(n_read == 0) {
                    printf("客户端断开连接!\n");
                    exit(0); // 退出子进程
                } else if(n_read > 0) {
                    printf("客户端发送消息:%s\n", msg.cmd); // 打印客户端消息
                    msg_handler(c_fd, msg);// 处理客户端消息
                }
            }
        }
    }
	
		return 0;
}
  • 代码分析
    • 头文件包含部分

这些头文件提供了网络通信、文件操作、字符串操作等功能所需的函数和类型定义。

    • 宏定义部分

定义了一系列宏,用于表示不同的命令类型,如 LSPWDGETPUT 等。

    • struct MSG 定义

定义了一个结构体 MSG,用于存储消息类型、命令字符串和额外的数据。

    • get_cmd_dir 函数

从命令字符串中提取文件路径。

    • cmd_type 函数

根据输入的命令字符串判断命令类型。

    • msg_handler 函数

根据命令类型执行相应的操作,如列出目录内容、获取文件、切换目录等。

    • 主函数 main
  1. 参数检查:检查命令行参数是否正确。
  2. 创建套接字:创建一个 TCP 服务器端套接字。
  3. 绑定套接字:将套接字绑定到服务器的 IP 地址和端口号。
  4. 监听连接:监听客户端的连接请求。
  5. 接受连接:接受客户端的连接请求,并创建子进程处理每个连接。
  6. 读取和处理消息:子进程循环读取客户端发送的消息,并调用 msg_handler 函数进行处理。
  • 实现了一个简单的 FTP 服务器,主要功能包括:
    • 命令处理
      • 支持基本的 FTP 命令,如 ls(列出目录)、pwd(当前工作目录)、cd(改变目录)、get(下载文件)和 put(上传文件)。
    • 客户端-服务器通信
      • 通过 TCP 套接字与客户端建立连接,接受客户端发送的命令,并根据命令执行相应的操作。
    • 多进程处理
      • 使用 fork 创建子进程,以处理多个客户端连接,实现并发。
    • 错误处理
      • 包括命令检查、文件存在性验证和基本的错误反馈。
    • 工作流程
      • 服务器启动并监听指定的 IP 和端口。
      • 接受客户端连接请求。
      • 在子进程中循环读取客户端发送的消息,识别命令类型,并调用相应的处理函数。
      • 根据命令执行操作并返回结果或反馈信息。
  • 客户端
#include <stdio.h>               // 包含标准输入输出头文件
#include <stdlib.h>              // 包含标准库函数头文件
#include <sys/types.h>           // 包含数据类型头文件
#include <sys/socket.h>          // 包含Socket通信头文件
#include <netinet/in.h>          // 包含Internet地址族头文件
#include <arpa/inet.h>           // 包含IP地址转换功能头文件
#include <string.h>              // 包含字符串操作函数头文件
#include <unistd.h>              // 包含unistd.h头文件
#include <sys/stat.h>            // 包含文件状态头文件
#include <fcntl.h>               // 包含文件控制头文件


#define LS 		0 		//	定义一个宏 LS,其值为 0。
#define PWD 	1 		//	定义一个宏 PWD,其值为 1。
#define GET 	2 		//	定义一个宏 GET,其值为 2。
#define IFGO 	3 		//	定义一个宏 IFGO,其值为 3。
#define CD 		4 		//	定义一个宏 CD,其值为 4。
#define PUT 	5		//	定义一个宏 PUT,其值为 5。
#define LLS 	6		//	定义一个宏 LLS,其值为 6。
#define LCD 	7		//	定义一个宏 LCD,其值为 7。
#define LPWD 	8		//	定义一个宏 LPWD,其值为 8。
#define QUIT 	9		//	定义一个宏 QUIT,其值为 9。
#define DOFILE	10		// 定义一个宏 DOFILE,其值为 10。

struct MSG{

//  定义了一个名为MSG的结构体,其中包含三个成员:

int type; 			  //	一个整型变量,用于表示消息类型。

char cmd[1024]; 	  //	一个长度为 1024 的字符数组,用于存储命令数据。

char secondBuf[1024]; //	一个长度为 1024 的字符数组,用于存储额外的数据。
};

char* getDir(char *cmd)                                                               //获取文件路径函数
{
    char *p = NULL;                                                                   //文件路径指针
 
    p = strtok(cmd, " ");                                                            //分割命令字
    p = strtok(NULL," ");                                                             //获取文件路径
    return p;
}


 
int cmd_type(char *cmd)                                                                    //命令类型判断函数
{
    if(!strcmp("ls", cmd))     return LS;                                                  //ls命令
    if(!strcmp("pwd", cmd))    return PWD;                                                 //pwd命令
    if(!strcmp("quit",cmd))    return QUIT;                                                //quit命令
    if(strstr(cmd,"cd"))       return CD;                                                  //cd命令
    if(strstr(cmd,"get"))      return GET;                                                 //get命令
    if(strstr(cmd,"put"))      return PUT;                                                 //put命令
 
    return -1;                                                                             //未知命令
}

int cmd_handler(int c_fd, struct MSG msg)                                               //命令处理函数
{
    int ret;                                                                            //返回值
    char *p = NULL;                                                                   //命令字指针
    char buf[128] = {0};                                                              //缓冲区
    int file_fd;                                                                       //文件描述符
    char *dir = NULL;  
	char* getDir(char *cmd);	//文件路径指针
 
    ret = cmd_type(msg.cmd);                                                          //获取命令类型 
    switch(ret){
        case LS:
        case PWD:
            msg.type = 0;                                                              //设置消息类型为0
            write(c_fd, &msg, sizeof(msg));                                            //发送消息   
            break;
        case CD:
            msg.type = 1;                                                              //设置消息类型为1
            write(c_fd, &msg, sizeof(msg));                                            //发送消息   
            break;
        case GET:
            msg.type = 2;                                                              //设置消息类型为2 
            write(c_fd, &msg, sizeof(msg));                                            //发送消息 
            break;
        case PUT:
            strcpy(buf, msg.cmd);                                   //拷贝将 msg.cmd 指向的命令到 buf 指向的缓冲区中、
			
			// 假设 msg.cmd 包含一个命令,例如 "get example"  // 将命令复制到 buf 中
			
			// 获取当前目录example 之后返回给p
            p = getDir(buf);        
			//获取文件路径
            if(access(p, F_OK) == -1){                                                 //判断文件是否存在
                printf("文件不存在\n");
            }else{
                file_fd = open(p, O_RDWR);                                            //可读可写方式打开文件
                read(file_fd, &msg.secondBuf, sizeof(msg.secondBuf));       //读文件内容到消息结构体的第二个缓冲区
                close(file_fd);                                                         //关闭文件
                write(c_fd, &msg, sizeof(msg));                                        //发送消息
            }
            break;
        case LCD:
            dir = getDir(msg.cmd);                                                      //获取文件路径
            if(chdir(dir) == -1){                                                     //切换目录
                printf("目录不存在\n");
            }
            break;
        case LLS:
            system("ls");                                                               //执行系统命令  
            break;
        case LPWD:
            system("pwd");                                                               //执行系统命令  
            break;
        case QUIT:
            strcpy(msg.cmd, "quit");                                                    //拷贝命令字到消息结构体
            write(c_fd, &msg, sizeof(msg));                                            //发送消息
            close(c_fd);                                                                //关闭套接字
            exit(-1);                                                                   //退出程序
            break;
    }   
 
    return ret;                                                                        //返回命令类型
}

void handler_sever_message(int c_fd, struct MSG msg)                                    //处理服务器消息函数
{
    int n_read;                                                                        //读取字节数
    struct MSG getmsg;                                                                //接收消息结构体 
    char *filename;                                                                   //文件名指针
    int newfile_fd;                                                                    //新文件描述符
 
    n_read = read(c_fd, &getmsg, sizeof(getmsg));                              //接收消息
    if(n_read == 0){
        printf("服务器关闭连接\n");
        exit(-1);
    }
    if(getmsg.type == DOFILE){      //当客户端输入的指令是GET时,需要在客户端创建需要获取的文件
        filename = getDir(msg.cmd);                                                      //获取文件名
        newfile_fd = open(filename, O_RDWR|O_CREAT, 0666);                              //创建新文件
        write(newfile_fd, getmsg.secondBuf, sizeof(getmsg.secondBuf));                    //写入文件内容
        close(newfile_fd);                                                             //关闭文件
        printf("文件%s上传成功\n", filename);                                            //打印提示信息
        fflush(stdout);                                                                 //刷新标准输出缓冲区
    }else{
        printf("------------------------------\n");
        printf("服务器消息: %s\n", getmsg.cmd);                                           //打印服务器消息
        printf("------------------------------\n");
    }
    
}


int main(int argc, char **argv)
{
    int c_fd;                                                                        //客户端套接字文件描述符
    int ret;                                                                         //返回值
 
    struct sockaddr_in c_addr;                                                   //客户端地址结构体
    struct MSG msg;                                                                  //消息结构体
 
    memset(&c_addr, 0, sizeof(c_addr));                                      //清零客户端地址结构体
 
    if(argc != 3){                                                                    //判断参数是否正确
        printf("参数错误,请按照格式输入: clientFTP IP 端口号\n");
        exit(-1);
    }
 
    //int socket(int domain, int type, int protocol);
    c_fd = socket(AF_INET, SOCK_STREAM, 0);                                            //创建客户端套接字
    if(c_fd == -1){
        printf("创建客户端套接字失败\n");
        perror("socket");
        exit(-1);
    }
 
    c_addr.sin_family = AF_INET;                                     //设置客户端地址结构体的地址族为IPv4
    c_addr.sin_port = htons(atoi(argv[2]));                                   //设置客户端地址结构体的端口号
    inet_aton(argv[1], &c_addr.sin_addr);                                      //设置客户端IP地址   
    //int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    ret = connect(c_fd, (struct sockaddr *)&c_addr, sizeof(c_addr));           //连接服务器
    if(ret == -1){
        printf("连接服务器失败\n");
        perror("connect");
        exit(-1);
    }
    printf("连接服务器成功\n");
 
    while(1){
        memset(&msg.cmd, 0, sizeof(msg.cmd));    //清零消息结构体的命令字
        printf("请输入命令字: \n");
      //  fgets(msg.cmd);     
		fgets(msg.cmd, sizeof(msg.cmd), stdin);                 //从标准输入获取命令字
		/*从用户输入中读取一行文本,最多读取 msg.cmd 的大小减去 1 个字符(以留出结束符),
		并在末尾自动加上一个 \0 字符来结束字符串。*/

		msg.cmd[strcspn(msg.cmd, "\n")] = 0; // 去掉换行符
        printf("------------------------------\n");
        printf("客户端消息: %s\n", msg.cmd);                  //打印客户端消息
        printf("------------------------------\n");
 
        ret = cmd_handler(c_fd, msg);                   //处理命令字
        if(ret > IFGO){
            fflush(stdout);                            //刷新标准输出缓冲区
            continue;                                 //继续循环
        }
        if(ret == -1){
            printf("未知命令类型\n");
            fflush(stdout);                           //刷新标准输出缓冲区
            continue;                                 //继续循环
        }
        
        handler_sever_message(c_fd, msg);             //处理服务器消息
    }
    return 0;
}
  • 代码分析:
    • 头文件包含部分

这些头文件提供了网络通信、文件操作、字符串操作等功能所需的函数和类型定义。

    • 宏定义部分

定义了一系列宏,用于表示不同的命令类型,如 LSPWDGETPUT 等。

    • struct MSG 定义

定义了一个结构体 MSG,用于存储消息类型、命令字符串和额外的数据。

    • getDir 函数

从命令字符串中提取文件路径。

    • cmd_type 函数

根据输入的命令字符串判断命令类型。

    • cmd_handler 函数

根据命令类型执行相应的操作,如列出目录内容、获取文件、切换目录等。

    • handler_sever_message 函数

处理从服务器接收到的消息,如保存文件内容。

  • 主函数 main
    1. 参数检查:检查命令行参数是否正确。
    2. 创建套接字:创建一个 TCP 客户端套接字。
    3. 连接服务器:使用服务器的 IP 地址和端口号连接到服务器。
    4. 命令循环:循环读取用户输入的命令,调用 cmd_handler 函数处理命令,然后调用 handler_sever_message 函数处理服务器的响应。
  1. 实现了一个简单的 FTP 客户端,主要功能包括:
    1. 命令输入与处理
      • 从用户获取输入命令,如 lspwdcdgetput 等,判断命令类型并进行相应处理。
    1. 与服务器通信
      • 通过 TCP 套接字连接到服务器,发送命令并接收响应。
    1. 文件操作
      • 支持上传和下载文件,处理文件路径和文件描述符。
    • 结构体定义
      • struct MSG:用于存储消息类型、命令和额外数据的结构体。
    • 命令类型判断
      • cmd_type 函数根据输入命令返回相应的命令类型。
    • 命令处理
      • cmd_handler 函数根据命令类型执行不同操作,比如改变目录、获取文件等。
    • 服务器消息处理
      • handler_sever_message 函数处理从服务器接收到的消息,执行相应的操作,如创建新文件。
    • 主函数
      • 创建套接字,连接服务器,并进入命令输入循环,不断获取用户输入并处理。
4.1. 代码功能
  1. FTP 服务器代码
    • 实现一个基础的 FTP 服务器,支持命令如 ls(列出目录)、pwd(当前工作目录)、cd(切换目录)、get(下载文件)和 put(上传文件)。
    • 处理客户端的请求,使用 fork 处理多个客户端连接。
    • 通过 TCP 套接字与客户端进行通信,接收和响应命令。
  1. FTP 客户端代码
    • 实现一个基础的 FTP 客户端,能够连接到 FTP 服务器并发送命令。
    • 支持输入命令并与服务器交互,处理文件的上传和下载。
    • 接收服务器返回的消息并执行相应操作。
4.2. 运行思路
  1. 服务器
    • 启动时创建套接字,绑定到指定的 IP 和端口,开始监听客户端连接。
    • 接受客户端连接请求,创建子进程处理各个客户端的命令。
    • 解析命令并执行相应操作,反馈结果给客户端。
  1. 客户端
    • 启动时创建套接字,连接到指定的服务器 IP 和端口。
    • 进入命令输入循环,从用户输入命令,发送给服务器并等待响应。
    • 根据服务器返回的消息,处理文件的创建、下载或其他操作。

点个赞吧!

标签:FTP,文件,int,网盘,cmd,char,msg,客户端
From: https://blog.csdn.net/2301_79405674/article/details/143369559

相关文章

  • c#使用FluentFTP来做FTP的上传下载
    最近由于项目需要,做了个FTP的上传下载上传///<summary>///上传共享文件///</summary>///<paramname="server">服务器信息</param>///<paramname="files">文件列表</param>publicstaticvoidUploadFtpFiles(FileServerConfigserver,......
  • VSFTP
    StorageSrv配置1服务​ 禁止使用不安全的FTP,请使用“CSKGlobalRootCA”证书颁发机构,颁发的证书,启用FTPS服务;​ 用户webadmin,登录ftp服务器,根目录为/webdata/;​ 登录后限制在自己的根目录;​ 允许WEB管理员上传和下载文件,但是禁止上传后缀名为.doc.docx.xlsx的文件。......
  • 盘点Air780E的FTP应用,你了解吗?
    ​ 一、FTP概述FTP(FileTransferProtocol,文件传输协议)是TCP/IP协议组中的协议之一。FTP协议包括两个组成部分,其一为FTP服务器,其二为FTP客户端。其中FTP服务器用来存储文件,用户可以使用FTP客户端通过FTP协议访问位于FTP服务器上的资源。在开发网站的时候,通常利用FTP协......
  • ubuntu ftp 服务器搭建及vsftpd.conf配置实例详解
    一、ftp服务器搭建与简单配置总结一下步骤吧:1、安装sudoapt-getinstallvsftpd可查看版本号命令vsftd-v2、修改配置文件/etc/vsftpd.conf根据具体的情况进行修改,去掉注释等,接下来会详细介绍。3、重启vsftpd服务sudoservicevsftpdrestart然后查看服务是否成......
  • vsftp的三种用户详解
    vsfp上有三种用户类型:annoymous匿名用户local_user本地用户virtual_user虚拟用户1、使用匿名用户不需要认证主配置文件中配置:anonymous_enable=YES2、使用本地用户本地用户,就是linux上的系统用户,满足下面两点就可以使用。1、用户的bash是/bin/bash2、主配置文件......
  • 低功耗4G模组:FTP应用示例
    ​一、FTP概述FTP(FileTransferProtocol,文件传输协议)是TCP/IP协议组中的协议之一。FTP协议包括两个组成部分,其一为FTP服务器,其二为FTP客户端。其中FTP服务器用来存储文件,用户可以使用FTP客户端通过FTP协议访问位于FTP服务器上的资源。在开发网站的时候,通常利用FTP协议......
  • Centos7.x搭建FTP文件服务器
    ##参考网址https://blog.csdn.net/wqh0830/article/details/87743928#1、创建用户名并指定家目录,useradd-d/home/ftpuser-s/sbin/nologinftpuseruseradd-d/home/ftpuser-gumpay-s/sbin/nologinftpuser#-d-->更改用户的家目录为/home/ftpuse;此目录与数据目录保持一致#-......
  • Linux下搭建sftp服务
    1.创建sftp组groupaddsftpcat/etc/group2.创建一个sftp用户zyfdsftp并加入到创建的sftp组中,同时修改zyfdsftp用户的密码useradd-gsftp-s/sbin/nologin-d/home/ftpuserzyfdsftppasswdzyfdsftp3.新建/sftp/zyfdsftp目录(需要在/下单独创建目录),并将它指定为......
  • 在Windows 10操作系统中搭建FTP
    在Windows10操作系统中搭建FTP(FileTransferProtocol,文件传输协议)服务器,可以为局域网内的用户提供文件共享和传输服务。以下是详细的搭建步骤,包括准备工作、安装与配置FTP服务、以及测试与访问FTP服务器等环节。一、准备工作在搭建FTP服务器之前,需要做好以下准备工作:确......
  • linux中的ftp服务有什么用
    Linux中的FTP服务是一种重要的网络协议,用于文件传输和共享。本文将深入探讨Linux中的FTP服务的作用,主要包括:1、文件传输功能;2、远程文件访问;3、文件备份和共享。Linux中的FTP服务允许用户在网络上快速、安全地传输文件。无论是将文件上传到服务器还是从服务器下载文件,FTP提供了高......