Linux 环境下使用 Socket 技术实现简化的 FTP服务器和客户端

前言

文件传输是计算机网络的基本功能,文件传输协议(File Transfer Protocol,FTP)是一个基本的应用层协议。

FTP 是 File Transfer Protocol 的简称,即文件传输协议的缩写。该协议用于在两台计算机之间传送文件。FTP 会话包含了两个通道,一个是控制通道,一个是数据通道。控制通道是和 FTP 服务器进行沟通的通道,连接 FTP 服务器,发送 FTP 指令;数据通道则是和 FTP服务器进行文件传输或者获取文件列表的通道。FTP 中,控制连接的各种指令均由客户端主动发起,而数据连接有两种工作方式:主动方式(PORT 方式)和被动方式(PASV 方式)。主动方式下,FTP 客户端首先和 FTP 服务器的控制通道对应端口(一般为 21)建立连接,通过控制通道发送命令,客户端需要接收数据的时候在这个通道上发送 PORT 命令。PORT 命令包含了客户端用什么端口(一个大于 1024的端口)接收数据。在传输数据的时候,FTP 服务器必须和客户端建立一个新的连接。被动方式下,建立控制通道的过程和主动方式类似,当客户端通过这个通道发送 PASV 命令的时候,FTP Server 打开一个位于 1024~5000 之间的随机端口并且通知客户端,然后客户端与服务器之间将通过这个端口进行数据的传送。具体的 FTP 规范请参考 RFC959。

需求分析

设计目的:

用高级语言编写和调试一个简单的 FTP 服务系统,掌握对进程、线程、同 步、通信、文件系统及网络编程的方法。从而加深学生对远程服务机制的理解和认识。

设计要求:

  1. 该系统要求有服务器端软件和客户端软件两部分组成,服务器端在指定端口接受客户连接 请求,根据客户要求执行相应处理,客户端提供系统的交互界面
  2. 基于套接字得客户、服务器通信模式
  3. 远程登录功能:要求集成 linux 系统的客户管理功能,对客户信息予以验证。
  4. 并发执行及管理功能:采用多线程,客户通过身份请求后创建一个新线程来响应客户请求。
  5. 文件管理功能 :服务器端的文件基本操作包括:mkdir、rmdir、cd、ls 、pwd
  6. 客户端执行的基本操作包括:创建及删除目录、切换目录、查看当前目录下的所有文 件
  7. 文件传输:upload/download 到指定目录。

开发环境

本程序分为服务器端和客户端两部分,全部在Ubuntu系统中使用 sublime编辑器配合GCC 编译执行。

程序特点

本程序主要使用套接口函数实现服务器端/客户端通信,具有较强的安全性设计和错误。采用多线程设计,可以保证多个客户端登陆同一服务器端而不冲突。 程序使用命令提示符界面,在指定账户登录可实现全部功能,而在匿名账户下可实现部 分功能。

总体设计

功能模块

本程序分为套接口通信模块、登录模块、命令解析模块、文件操作模块、文件传输模块 共 5 个模块,具体如下:

服务器客户端通信模块

序号 函数定义 功能
1 void handle_pasv(int client_sock, struct sockaddr_in client) 设定ftp工作模式
2 void *Handle_Client_Request(void *arg) 响应客户端请求
3 void do_client_work(int client_sock, struct sockaddr_in client) 处理客户端工作
4 void send_client_info(int client_sock, char* info, int length); 发送客户端信息
5 int recv_client_info(int client_sock); 接收客户端信息
6 int fill_host_addr(char * host_ip_addr, struct sockaddr_in * host, int port) 写入服务器地址
7 int ftp_get_reply(int sock_fd) 接收服务端回应
8 int ftp_send_cmd(const char *s1, const char *s2, int sock_fd) 发送ftp命令
9 int xconnect(struct sockaddr_in * s_addr, int type) 连接服务器与客户端
10 int xconnect_ftpdata() 传输数据
11 struct sockaddr_in create_date_sock() 创建数据管道

登录模块

序号 函数定义 功能
1 int ftp_login() 登录到ftp服务器
2 int login() 客户端登录
3 void get_user() 获取用户名
4 void get_pass() 获取密码
5 void close_cli() 关闭客户端连接
6 void show_help() 显示帮助信息
7 void ftp_quit() 退出客户端

命令解析模块

序号 函数定义 功能
1 int ftp_user_cmd(char * user_cmd) 解析用户输入的命令
2 int start_ftp_cmd(char * host_ip_addr, int port) 连接到服务器的命令
3 void cmd_err_exit(char * err_msg, int err_code) 处理错误的命令
4 void ftp_cmd_filename(char * user_cmd, char * src_file, char * dst_file) 处理文件名的命令

文件传输模块

序号 函数定义 功能
1 void ftp_put(char * user_cmd) 上传文件
2 void ftp_get(char * user_cmd) 下载文件
3 void handle_file(int client_sock) 检查文件
4 void handle_del(int client_sock) 删除文件

文件操作模块

序号 函数名 功能
1 void handle_cwd(int client_sock) 处理转换目录的请求
2 void handle_rmd(int client_sock) 处理删除目录的请求
3 void handle_mkd(int client_sock) 处理新建文件夹的请求
4 void handle_list(int client_sock) 处理列出文件列表的请求
5 void ftp_list() 列出服务器文件列表
6 void ftp_pwd() 显示服务器当前所在目录
7 void ftp_cd() 转到服务器指定目录
8 void del() 删除文件
9 void mkdir_srv() 新建文件夹
10 void rmdir_srv() 删除文件夹
11 void local_list() 列出客户端文件列表
12 void local_pwd() 显示客户端当前所在目录
13 void local_cd() 转到客户端指定目录

程序流程图

创建商业退款逻辑流程图 (1)

详细实现方法

获取用户名密码

相关代码

//获取用户名
void get_user(){ 
	char read_buf[64];
	printf("User(Press for anonymous): ");
	fgets(read_buf, sizeof(read_buf), stdin);

	if(read_buf[0] == '\n')
		strncpy(user, "anonymous", 9);
	else
		strncpy(user, read_buf, strlen(read_buf) - 1);
}

//获取密码
void get_pass(){
	char read_buf[64];

	printf("Password(Press for anonymous): ");
	echo_off();

	fgets(read_buf, sizeof(read_buf), stdin);

	if(read_buf[0] == '\n')
		strncpy(passwd, "anonymous", 9);
	else
		strncpy(passwd, read_buf, strlen(read_buf) - 1);

	echo_on();

	printf("\n");
}

//登录ftp
int ftp_login(){
	int err;

	get_user();

	if(ftp_send_cmd("USER ", user, sock_control) < 0)
		cmd_err_exit("Can not send message", 1);

	err = ftp_get_reply(sock_control);

	if(err == 331){
		get_pass();

		if(ftp_send_cmd("PASS ", passwd, sock_control) <= 0)
			cmd_err_exit("Can not send message", 1);

		else
			err = ftp_get_reply(sock_control);

		if(err == 230)
			return 1;
		else if(err == 531)
			return 1;
		else{
			printf("Password error!\n");
			return 0;
		}
	}

	else{
		printf("User error!\n");
		return 0;
	}
}

测试结果

image-20200620155048763

连接服务器

相关代码

//连接到服务器
int xconnect(struct sockaddr_in * s_addr, int type){
	struct timeval outtime;
	int set;
	int s = socket(AF_INET, SOCK_STREAM, 0);
	if(s < 0){
		cmd_err_exit("Create socket error!", 249);
	}

	if(type == 1){
		outtime.tv_sec = 0;
		outtime.tv_usec = 300000;
	}
	else{
		outtime.tv_sec = 5;
		outtime.tv_usec = 0;
	}

	set = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &outtime, sizeof(outtime));

	if(set != 0){
		printf("Set socket %s errno:%d\n", strerror(errno), errno);
		cmd_err_exit("set socket", 1);
	}

	//连接到服务器,如果连接成功,connect函数返回0,如果不成功,返回-1。
	if(connect(s, (struct sockaddr *)s_addr, sizeof(struct sockaddr_in)) < 0){
		printf("Can't connect to server %s, prot %d\n", inet_ntoa(s_addr -> sin_addr), ntohs(ftp_server.sin_port));
		exit(252);
	}
	return s;
}

//发送 FTP 指令
int ftp_send_cmd(const char *s1, const char *s2, int sock_fd){
	char send_buf[256];
	int send_err, len;

	if(s1){
		strcpy(send_buf, s1);
		if(s2){
			strcat(send_buf, s2);
			strcat(send_buf, "\r\n");
			len = strlen(send_buf);
			send_err = send(sock_fd, send_buf, len, 0);
		}
		else{
			strcat(send_buf, "\r\n");
			len = strlen(send_buf);
			send_err = send(sock_fd, send_buf, len, 0);
		}
	}

	if(send_err < 0){
		printf("Send() error!\n");
	}
	return send_err;
}

//接收服务器返回信息
int ftp_get_reply(int sock_fd){
	static int reply_code = 0, count = 0;
	char rcv_buf[512];
	count = read(sock_fd, rcv_buf, 510);
	if(count > 0){
		reply_code = atoi(rcv_buf);
	}
	else{
		return 0;
	}

	while(1){
		if(count <= 0){
			break;
		}

		rcv_buf[count] = '\0';
		printf("%s\n", rcv_buf);
		count = read(sock_fd, rcv_buf, 510);
	}
	return reply_code;
}



int get_port(){
	char port_respond[512];

	char *buf_ptr;

	int count, port_num;

	ftp_send_cmd("PASV", NULL, sock_control);

	count = read(sock_control, port_respond, 510);

	if(count <= 0){
		return 0;
	}

	port_respond[count] = '\0';
	if(atoi(port_respond) == 227){
		buf_ptr = strrchr(port_respond, ',');
		port_num = atoi(buf_ptr + 1);

		*buf_ptr = '\0';

		buf_ptr = strrchr(port_respond, ',');
		port_num += atoi(buf_ptr + 1) * 256;
		return port_num;
	}

	return 0;
}


int rand_local_port(){

	int local_port;

	srand((unsigned)time(NULL));
	local_port = rand() % 40000 + 1025;  //0~1024是系统保留端口号
	return local_port;
}

测试结果

image-20200620155711890

连接服务器数据流

相关代码

//连接服务器数据流
int xconnect_ftpdata(){

	//PASV
	if(mode){
		int data_port = get_port();
		if(data_port != 0)
			ftp_server.sin_port = htons(data_port);

		return(xconnect(&ftp_server, 0));
	}
	//PORT
	else{
		int client_port, get_sock, opt, set;

		char cmd_buf[32];

		struct timeval outtime;

		struct sockaddr_in local;

		char local_ip[24];

		char *ip_1, *ip_2, *ip_3, *ip_4;
		
		int addr_len = sizeof(struct sockaddr);
		client_port = rand_local_port();
		get_sock = socket(AF_INET, SOCK_STREAM, 0);

		if(get_sock < 0){
			cmd_err_exit("socket()", 1);
		}



		outtime.tv_sec = 7;
		outtime.tv_usec = 0;

		opt = SO_REUSEADDR;

		set = setsockopt(get_sock, SOL_SOCKET, SO_RCVTIMEO, &outtime, sizeof(outtime));

		if(set != 0){
			printf("Set socket %s errno:%d\n", strerror(errno), errno);
			cmd_err_exit("set socket", 1);
		}

		set = setsockopt(get_sock, SOL_SOCKET, SO_REUSEADDR, &opt,sizeof(opt));
		if(set !=0)
		{
			printf("set socket %s errno:%d\n",strerror(errno),errno);
			cmd_err_exit("set socket", 1);
		}

		bzero(&local_host, sizeof(local_host));

		local_host.sin_family = AF_INET;
		local_host.sin_port = htons(client_port);;

		local_host.sin_addr.s_addr = htonl(INADDR_ANY);

		bzero(&local, sizeof(struct sockaddr));

		while(1){
			set = bind(get_sock, (struct sockaddr *)& local_host, sizeof(local_host));

			if(set != 0 && errno == 11){
				client_port = rand_local_port();
				continue;
			}

			set = listen(get_sock, 1);
			if(set != 0 && errno == 11){
				cmd_err_exit("listen()", 1);
			}

			//获取ip地址
			if(getsockname(sock_control, (struct sockaddr *)&local, (socklen_t *)&addr_len) < 0)
				return -1;

			snprintf(local_ip, sizeof(local_ip), inet_ntoa(local.sin_addr));

			local_ip[strlen(local_ip)] = '\0';

			ip_1 = local_ip;

			ip_2 = strchr(local_ip, '.');

			*ip_2 = '\0';

			ip_2++;

			ip_3 = strchr(ip_2, '.');

			*ip_3 = '\0';

			ip_3++;

			ip_4 = strchr(ip_3, '.');

			*ip_4 = '\0';

			ip_4++;

			snprintf(cmd_buf, sizeof(cmd_buf), "PORT %s, %s, %s, %s, %d, %d", ip_1, ip_2, ip_3, ip_4, client_port >> 8, client_port & 0xff);
			ftp_send_cmd(cmd_buf, NULL, sock_control);

			if(ftp_get_reply(sock_control) != 200){
				printf("Can not use PORT mode! Please use \"mode\" change to PASV mode.\n");
				return -1;
			}
			else
				return get_sock;

		}
	}
}

向服务器发送客户端命令

相关代码

//发送 FTP 指令
int ftp_send_cmd(const char *s1, const char *s2, int sock_fd){
	char send_buf[256];
	int send_err, len;

	if(s1){
		strcpy(send_buf, s1);
		if(s2){
			strcat(send_buf, s2);
			strcat(send_buf, "\r\n");
			len = strlen(send_buf);
			send_err = send(sock_fd, send_buf, len, 0);
		}
		else{
			strcat(send_buf, "\r\n");
			len = strlen(send_buf);
			send_err = send(sock_fd, send_buf, len, 0);
		}
	}

	if(send_err < 0){
		printf("Send() error!\n");
	}
	return send_err;
}

接收服务器返回信息

相关代码

//接收服务器返回信息
int ftp_get_reply(int sock_fd){
	static int reply_code = 0, count = 0;
	char rcv_buf[512];
	count = read(sock_fd, rcv_buf, 510);
	if(count > 0){
		reply_code = atoi(rcv_buf);
	}
	else{
		return 0;
	}

	while(1){
		if(count <= 0){
			break;
		}

		rcv_buf[count] = '\0';
		printf("%s\n", rcv_buf);
		count = read(sock_fd, rcv_buf, 510);
	}
	return reply_code;
}

显示服务器当前目录下文件

相关代码

//显示服务器当前目录下文件
void ftp_list(){
	int i = 0, new_sock;
	int set = sizeof(local_host);

	int list_sock_data = xconnect_ftpdata();

	if(list_sock_data < 0){
		ftp_get_reply(sock_control);

		printf("Create data sock error!\n");

		return;
	}

	ftp_get_reply(sock_control);

	ftp_send_cmd("LIST", NULL, sock_control);

	ftp_get_reply(sock_control);

	if(mode)
		//PASV
		ftp_get_reply(list_sock_data);
	else{
		//PORT
		while(i < 3){
			new_sock = accept(list_sock_data, (struct sockaddr *)& local_host, (socklen_t *)& set);
			if(new_sock == -1){
				printf("Accept return:%s errno:%d\n", strerror(errno), errno);
				i++;
				continue;
			}
			else
				break;
		}

		if(new_sock == -1){
			printf("Sorry, you can not use PORT mode. There is something wrong when the server connect to you.\n");
			return;
		}

		ftp_get_reply(new_sock);
		close(new_sock);
	}


	close(list_sock_data);
	ftp_get_reply(sock_control);
}

测试结果

image-20200620195740985

列出客户端当前目录下文件

相关代码

void local_list(){
	DIR * dp;

	struct dirent * dirp;

	if((dp = opendir("./")) == NULL){
		printf("Opendir() error!\n");
		return;
	}

	printf("Local file list:\n");

	while((dirp = readdir(dp)) != NULL){
		if(strcmp(dirp -> d_name, ".") == 0 || strcmp(dirp -> d_name, "..") == 0){
			continue;
		}

		printf("%s\n", dirp -> d_name);
	}
}

测试结果

image-20200620200642572

显示客户端目录

相关代码

void local_pwd(){
	char curr_dir[512];

	int size = sizeof(curr_dir);
	
    //得到客户端目录
	if(getcwd(curr_dir, size) == NULL)
		printf("Getcwd failed\n");
	else
		printf("Current local directory: %s\n", curr_dir);
}

测试结果

image-20200620200017917

扫描二维码关注公众号,回复: 11327438 查看本文章

处理客户端需求

相关代码

//处理客户需求,从客户端连接成功开始到结束服务
void *Handle_Client_Request(void *arg){
    struct ARG*info;
    info = (struct ARG*)arg;

    printf("You got a connection from %s\n", inet_ntoa(info->client.sin_addr));

    do_client_work(info -> client_sock, info -> client);

    close(info -> client_sock);

    pthread_exit(NULL);
}

处理FTP各种命令

相关代码

// 与客户端交互
void do_client_work(int client_sock, struct  sockaddr_in client){
    int login_flag;

    login_flag = login(client_sock);

    while(recv_client_info(client_sock) && login_flag == 1){

        if((strncmp("quit", client_Control_Info, 4) == 0) || (strncmp("QUIT", client_Control_Info, 4) == 0)){

            send_client_info(client_sock, serverInfo221, strlen(serverInfo221));
            break;
        }
        else if((strncmp("close", client_Control_Info, 5) == 0) || (strncmp("CLOSE", client_Control_Info, 5) == 0)){
            printf("Client quit!\n");
            shutdown(client_sock, SHUT_WR);  // 断开输出流
        }

        else if((strncmp("pwd", client_Control_Info, 3) == 0) || (strncmp("PWD", client_Control_Info, 3) == 0)){
            char pwd_info[MSG_INFO];
            char tmp_dir[DIR_INFO];

            snprintf(pwd_info, MSG_INFO, "257 \"%s\" is current location. \r\n", getcwd(tmp_dir, DIR_INFO));
            send_client_info(client_sock, pwd_info, strlen(pwd_info));
        }
        else if((strncmp("cwd", client_Control_Info, 3) == 0) || (strncmp("CWD", client_Control_Info, 3) == 0)){
            handle_cwd(client_sock);
        }
        else if((strncmp("mkd", client_Control_Info, 3) == 0) || (strncmp("MKD", client_Control_Info, 3) == 0)){
            handle_mkd(client_sock);
        }
        else if((strncmp("rmd", client_Control_Info, 3) == 0) || (strncmp("RMD", client_Control_Info, 3) == 0)){
            handle_rmd(client_sock);
        }
        else if((strncmp("dele", client_Control_Info, 4) == 0) || (strncmp("DELE", client_Control_Info, 4) == 0)){
            handle_del(client_sock);
        }
        else if((strncmp("pasv", client_Control_Info, 4) == 0) || (strncmp("PASV", client_Control_Info, 4) == 0)){
            handle_pasv(client_sock, client);
        }

        else if((strncmp("list", client_Control_Info, 4) == 0) || (strncmp("LIST", client_Control_Info, 4) == 0)){
            handle_list(client_sock);
            send_client_info(client_sock, serverInfo226, strlen(serverInfo226));

        }
        else if((strncmp("type", client_Control_Info, 4) == 0) || (strncmp("TYPE", client_Control_Info, 4) == 0)){
            if((strncmp("type I", client_Control_Info, 6) == 0) ||(strncmp("TYPE I", client_Control_Info, 6) == 0)){
                translate_data_mode = FILE_TRANS_MODE_BIN;
            }
            send_client_info(client_sock, serverInfo200, strlen(serverInfo200));
        }
        else if((strncmp("retr", client_Control_Info, 4) == 0)||(strncmp("RETR", client_Control_Info, 4) == 0)){
            handle_file(client_sock);
            send_client_info(client_sock, serverInfo226, strlen(serverInfo226));
        }
        else if(strncmp("stor", client_Control_Info, 4) == 0||(strncmp("STOR", client_Control_Info, 4) == 0)){
            handle_file(client_sock);
            send_client_info(client_sock,serverInfo226, strlen(serverInfo226));
        }
        else if(strncmp("syst", client_Control_Info, 4) == 0||(strncmp("SYST", client_Control_Info, 4) == 0)){
            send_client_info(client_sock, serverInfo215, strlen(serverInfo215));
        }
        else if(strncmp("size", client_Control_Info, 4) == 0||(strncmp("SIZE", client_Control_Info, 4) == 0)){
            send_client_info(client_sock, serverInfo213, strlen(serverInfo213));
        }
        else if(strncmp("feat", client_Control_Info, 4) == 0||(strncmp("FEAT", client_Control_Info, 4) == 0)){
            send_client_info(client_sock, serverInfo211, strlen(serverInfo211));
        }
        else if(strncmp("rest", client_Control_Info, 4) == 0||(strncmp("REST", client_Control_Info, 4) == 0)){
            send_client_info(client_sock, serverInfo350, strlen(serverInfo350));
        }
        else {
            send_client_info(client_sock, serverInfo, strlen(serverInfo));
        }
    }

    while(recv_client_info(client_sock) && (login_flag == 2)){
        
        
        if((strncmp("quit", client_Control_Info, 4) == 0)||(strncmp("QUIT", client_Control_Info, 4) ==0)){
            send_client_info(client_sock, serverInfo221, strlen(serverInfo221));
            break;
        }
        else if((strncmp("close",client_Control_Info, 5) == 0)||(strncmp("CLOSE",client_Control_Info, 5) == 0)){
            printf("Client Quit!\n");
            shutdown(client_sock, SHUT_WR); // 断开输出流
        }

        else if((strncmp("pwd", client_Control_Info, 3) == 0)||(strncmp("PWD", client_Control_Info, 3) == 0)){
            char pwd_info[MSG_INFO];
            char tmp_dir[DIR_INFO];
            snprintf(pwd_info, MSG_INFO, "257 \"%s\" is current location.\r\n", getcwd(tmp_dir, DIR_INFO));
            send_client_info(client_sock, pwd_info, strlen(pwd_info));
        }
        else if((strncmp("cwd", client_Control_Info, 3) == 0)||(strncmp("CWD", client_Control_Info, 3) == 0)){
            handle_cwd(client_sock);
        }
        else if((strncmp("pasv", client_Control_Info, 4) == 0)||(strncmp("PASV", client_Control_Info, 4)== 0)){
            handle_pasv(client_sock,client);
        }
        else if((strncmp("list", client_Control_Info, 4) == 0)||(strncmp("LIST", client_Control_Info, 4)== 0)){
            handle_list(client_sock);
            send_client_info(client_sock, serverInfo226, strlen(serverInfo226));
        }
        else if((strncmp("type", client_Control_Info, 4) == 0)||(strncmp("TYPE", client_Control_Info, 4)== 0)){
            if((strncmp("type I", client_Control_Info, 6) == 0)||(strncmp("TYPE I", client_Control_Info, 6) == 0)) {
                translate_data_mode = FILE_TRANS_MODE_BIN;
            }
            send_client_info(client_sock, serverInfo200, strlen(serverInfo200));
        }
        else if((strncmp("retr", client_Control_Info, 4) == 0)||(strncmp("RETR", client_Control_Info, 4)== 0)){
            handle_file(client_sock);
            send_client_info(client_sock,serverInfo226, strlen(serverInfo226));
        }
        else if((strncmp("syst", client_Control_Info, 4) == 0)||(strncmp("SYST", client_Control_Info, 4)== 0)){
            send_client_info(client_sock, serverInfo215, strlen(serverInfo215));
        }
        else if((strncmp("size", client_Control_Info, 4) == 0)||(strncmp("SIZE", client_Control_Info, 4)== 0)){
            send_client_info(client_sock, serverInfo213, strlen(serverInfo213));
        }
        else if((strncmp("feat", client_Control_Info, 4) == 0)||(strncmp("FEAT", client_Control_Info, 4)== 0)){
            send_client_info(client_sock, serverInfo211, strlen(serverInfo211));
        }
        else if((strncmp("rest", client_Control_Info, 4) == 0)||(strncmp("REST", client_Control_Info, 4)== 0)){
            send_client_info(client_sock, serverInfo350, strlen(serverInfo350));
        }
        else{
            send_client_info(client_sock, serverInfo, strlen(serverInfo));
        }
    }
}

创建数据sock

相关代码

//)创建数据sock。
struct sockaddr_in create_date_sock(){
	int t_client_sock;

	struct sockaddr_in t_data_addr;

	t_client_sock = socket(AF_INET, SOCK_STREAM, 0);

	if(t_client_sock < 0){
		printf("Creating data socket error!\n");
		return;
	}

	srand((int)time(0));

	int a = rand() % 1000 + 1025;

	bzero(&t_data_addr, sizeof(t_data_addr));

	t_data_addr.sin_family = AF_INET;

	t_data_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	t_data_addr.sin_port = htons(a);

	if(bind(t_client_sock, (struct sockaddr *)&t_data_addr, sizeof(struct sockaddr)) < 0){
		printf("Bind error in create data socket:%s\n", strerror(errno));
		return;
	}

	listen(t_client_sock, LISTEN_QENU);

	ftp_data_sock = t_client_sock;

	return t_data_addr;
}

服务器处理文件

相关代码

// 处理文件类命令,把上传下载功能集成在一个函数中实现
void handle_file(int client_sock){

	send_client_info(client_sock, serverInfo150, strlen(serverInfo150));

	int t_data_sock;

	struct sockaddr_in client;

	int sin_size = sizeof(struct sockaddr_in);
	
	if((t_data_sock = accept(ftp_data_sock, (struct sockaddr *)&client, &sin_size)) == -1){
		perror("Accept error");
		return;
	}

	int i = 0;

	int length = strlen(client_Control_Info);

	//格式化命令
	for(i = 5; i < length; i++){
		format_client_Info[i - 5] = client_Control_Info[i];
	}

	format_client_Info[i - 7] = '\0';


	FILE* fp;  //建立管道处理文件信息

	int file_fd;

	int n;

	char t_dir[DIR_INFO];

	char file_info[DIR_INFO];

	snprintf(file_info, DIR_INFO, "%s/%s", getcwd(t_dir, DIR_INFO), format_client_Info);  //获取文件信息

	char file_mode[3];


	if((strncmp("retr", client_Control_Info, 4) == 0) || (strncmp("RETR", client_Control_Info, 4) == 0)){
		//打开文件方式
		file_mode[0] = 'r';
		file_mode[1] = 'b';
		file_mode[2] = '\0';
	}
	else{
		file_mode[0] = 'a';
		file_mode[1] = 'b';
		file_mode[2] = '\0';
	}

	if(strncmp(getcwd(t_dir, DIR_INFO), format_client_Info, strlen(getcwd(t_dir, DIR_INFO)) - 1) == 0){
		
		fp = fopen(format_client_Info, file_mode); //打开文件
	}
	else{
		fp = fopen(file_info, file_mode);
	}

	if(fp == NULL){
		printf("Open file error: %s\r\n", strerror(errno));

		char cwd_info[MSG_INFO];

		snprintf(cwd_info, MSG_INFO, "550 %s : %s\r\n", format_client_Info, strerror(errno));
		send_client_info(client_sock, cwd_info,strlen(cwd_info));
		close(t_data_sock);

		close(ftp_data_sock);
		return;
	}

	int cmd_sock = fileno(fp); //获取文件描述词

	memset(client_Data_Info, 0, MSG_INFO);

	//下载文件
	if((strncmp("retr", client_Control_Info, 4) == 0) || (strncmp("RETR", client_Control_Info, 4) == 0)){
		while((n = read(cmd_sock, client_Data_Info, MSG_INFO)) > 0){  //读写文件
			if(write(t_data_sock, client_Data_Info, n) != n){
				printf("Retr transfer error!\n");
				return;
			}
		}
	}

	//上传文件
	else{
		while((n = read(t_data_sock, client_Data_Info, MAX_INFO)) > 0){
			if(write(cmd_sock, client_Data_Info, n) != n){
				printf("Stor transfer error!\n");
				return;
			}
		}
	}


	fclose(fp);  //关闭传输管道

	close(t_data_sock);
	close(ftp_data_sock);
}

发送、接收客户端信息

相关代码

//发送客户端信息。
void send_client_info(int client_sock, char* info, int length){
	int len;
	if((len = send(client_sock, info, length, 0)) < 0){
		perror("Send info error!");
		return;
	}
}

//接收客户端信息。
int recv_client_info(int client_sock){
	int num;
	if((num = recv(client_sock, client_Control_Info, MSG_INFO, 0)) < 0){
		perror("Receive info error!");
		return;
	}

	client_Control_Info[num] = '\0';

	printf("Client %ld Message: %s\n", pthread_self(), client_Control_Info);
	if((strncmp("USER", client_Control_Info, 4) == 0) || (strncmp("user", client_Control_Info, 4) == 0)){
		return 2;
	}
	return 1;
}

P.S.

多客户端同时登录

采用多线程设计,可以保证多个客户端登陆同一服务器端而不冲突。

相关代码

while(1){
    if((ftp_client_sock = accept(ftp_server_sock, (struct sockaddr *)&client, &sin_size)) == -1){
        perror("Accept error!");
        exit(1);
    }

    arg.client_sock = ftp_client_sock;

    memcpy((void*)&arg.client, &client, sizeof(client));

    if(pthread_create(&thread, NULL, Handle_Client_Request, (void*)&arg)){
        perror("Thread create error!");
        exit(1);
    }
}

测试结果

image-20200620212536675

FTP规范

相关代码

char serverInfo220[]="220 myFTP Server ready...\r\n";
char serverInfo230[]="230 User logged in, proceed.\r\n";
char serverInfo331[]="331 User name okay, need password.\r\n";
char serverInfo221[]="221 Goodbye!\r\n";
char serverInfo213[]="213 File status.\r\n";
char serverInfo211[]="211 System status, or system help reply.\r\n";
char serverInfo350[]="350 Requested file action pending further information.\r\n";
char serverInfo530[]="530 Not logged in.\r\n";
char serverInfo531[]="531 Not root client. Anonymous client.\r\n";
char serverInfo[]="202 Command not implemented, superfluous at this site.\r\n";
char serverInfo150[]="150 File status okay; about to open data connection.\r\n";
char serverInfo226[]="226 Closing data connection.\r\n";
char serverInfo200[]="200 Command okay.\r\n";
char serverInfo215[]="215 Unix Type FC5.\r\n";

猜你喜欢

转载自www.cnblogs.com/lihello/p/13170538.html