基于Socket套接字的FTP服务简单实现

基于Socket套接字的FTP服务简单实现

文件传输协议(File Transfer Protocol,FTP)是用于在网络上进行文件传输的一套标准协议,它工作在 OSI 模型的第七层, TCP 模型的第四层, 即应用层, 使用 TCP 传输而不是 UDP, 客户在和服务器建立连接前要经过一个“三次握手”的过程, 保证客户与服务器之间的连接是可靠的, 而且是面向连接, 为数据传输提供可靠保证。

一、项目简介

1、利用套接字实现客户端和服务端的连接;
2、服务端和客户端可以互传文件;
3、客户端和服务端信息交互实现类似Linux系统下vi的快捷指令操作。

二、项目具体实现功能

1、客户端本地功能(xx代表文件或者文件夹名称):

  • 客户端查看当前文件路径:lpwd
  • 查看客户端当前目录下所有本地文件:lls
  • 客户端进入本地文件夹:lcd xx

2、客户端和服务端远程功能:

  • 客户端查看服务端的当前文件路径:pwd
  • 客户端查看服务端当前路径下所有文件:ls
  • 客户端进入服务端文件夹:cd xx
  • 客户端上传文件至服务端:upload xx
  • 客户端下载服务端文件到本地:download xx

三、代码实现

1、服务端代码实现:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <fcntl.h>

//宏定义相关端指令
#define QUIT      0
#define LS        1
#define PWD       2
#define LLS       3
#define LPWD      4
#define LCD       5
#define CD        6
#define UPLOAD    7
#define DOWNLOAD  8     

struct Msg{
    
    
        int type;
        char cmd[128];
        char filename[128];
        char data[1024];
}msg;


char *getDir(char *cmd);//分割指令和文件,返回文件/文件夹名称
int analyse_Cmd(char *cmd);//分析指令
void getCmd(int c_fd,struct Msg msg);//根据指令做出响应

int main(int argc, char **argv)
{
    
    
	int s_fd;
	int c_fd;
	
	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;
	int s_len = sizeof(struct sockaddr_in);
	int c_len = sizeof(struct sockaddr_in);
	
	memset(&s_addr,'\0',s_len);
	memset(&c_addr,'\0',c_len);
	
	//1.socket
	if((s_fd = socket(AF_INET,SOCK_STREAM,0)) == -1){
    
    
	        printf("creat socket failed\n");
	        exit(-1);
	}
	//2.bind
	s_addr.sin_family = AF_INET;
	s_addr.sin_port = htons(atoi(argv[2]));
	inet_aton(argv[1],&s_addr.sin_addr);
	
	if(bind(s_fd,(struct sockaddr *)&s_addr,s_len) != 0){
    
    
	        perror("bind");
	        exit(-1);
	}
	//3.listen
	if(listen(s_fd,10) != 0){
    
    
	        perror("listen");
	        exit(-1);
	}
	while(1){
    
    
		printf("wait connect -----\n");
		//4.accept
		if((c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&c_len)) == -1){
    
    
			perror("accept");
			exit(-1);
	    }else{
    
    
	        printf("get connect %s\n",inet_ntoa(c_addr.sin_addr));
	    }
	
	//创建子进程专门处理指令
	if(fork() == 0){
    
    
		 while(1){
    
    
			memset(&msg,'\0',sizeof(struct Msg));
			read(c_fd,&msg,sizeof(struct Msg));
			printf("cmd:%s\n",msg.cmd);
			getCmd(c_fd,msg);
		}
	}
	close(s_fd);
	close(c_fd);
	return 0;
}

//将指令和文件名称分割,得到文件/文件夹名称
char *getDir(char *cmd)
{
    
    
	char *p;
	p = strtok(cmd," ");
	p = strtok(NULL," ");
	return p;
}

//分析指令
int analyse_Cmd(char *cmd)
{
    
    
	if(!strcmp(cmd,"quit"))         return QUIT;
	if(!strcmp(cmd,"ls"))           return LS;
	if(!strcmp(cmd,"pwd"))          return PWD;
	
	if(strstr(cmd,"cd"))            return CD;
	if(strstr(cmd,"upload"))        return UPLOAD;
	if(strstr(cmd,"download"))      return DOWNLOAD;
	return -1;
}

//根据指令实现相应功能
void getCmd(int c_fd, struct Msg msg)
{
    
    
	int fd;
	FILE *fp;
	char *pret;
	char readBuf[1024] = {
    
    0};
	int ret = analyse_Cmd(msg.cmd);
	switch(ret)
	{
    
    
		case LS:
		case PWD:
			fp = popen(msg.cmd,"r");
			fread(msg.data,1,sizeof(msg.data),fp);
			write(c_fd,&msg,sizeof(struct Msg));
			pclose(fp);
			printf("msg.data \n %s\n",msg.data);
			break;
		
		case CD:
			pret = getDir(msg.cmd);
			if(chdir(pret) == -1){
    
    
			        perror("chdir");
			}
			getcwd(msg.data,sizeof(msg.data));
			write(c_fd,&msg,sizeof(struct Msg));
		break;
		
		case UPLOAD:
			pret = getDir(msg.filename);
			fd = open(pret,O_RDWR|O_CREAT,0600);
			write(fd,msg.data,strlen(msg.data)+8);
			close(fd);
			printf("upload success\n");
		break;
		
		case DOWNLOAD:
			msg.type = DOWNLOAD;
			strcpy(msg.filename,msg.cmd);
			pret = getDir(msg.cmd);
			if(open(pret,O_RDWR) < 0 ){
    
    
			        write(c_fd,"Fiel not found",20);
			        exit(-1);
			}else{
    
    
			        fd = open(pret,O_RDWR);
			        read(fd,readBuf,sizeof(readBuf));
			        strcpy(msg.data,readBuf);
			        write(c_fd,&msg,sizeof(struct Msg));
			        close(fd);
			}
			break;
		
		case QUIT:
			printf("client quit!\n");
			exit(1);
			break;
	}
}                                                                       

2、客户端代码实现:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

//宏定义相关指令
#define QUIT      0
#define LS        1
#define PWD       2
#define LLS       3
#define LPWD      4
#define LCD       5
#define CD        6
#define UPLOAD    7
#define DOWNLOAD  8     


struct Msg{
    
    
	int  type;
	char cmd[128];
	char filename[128];
	char data[1024];
}msg;

char *getDir(char *cmd);//分割指令和文件夹名称,返回文件/文件夹名称
int analyse_Cmd(char *cmd);//分析客户端发出的指令
void cmd_Handler(int c_fd, struct Msg msg);//处理客户端的指令
void getData(int c_fd, struct Msg msg);//得到服务端返回的数据

int main(int argc, char **argv)
{
    
    
	int c_fd;
	int mark = 0;
	struct sockaddr_in c_addr;
	int c_len = sizeof(struct sockaddr_in);
	memset(&c_addr,'\0',c_len);

	//1.socket
	if((c_fd = socket(AF_INET,SOCK_STREAM,0)) == -1){
    
    
		printf("creat socket failed\n");
		exit(-1);
	}
	//2.connect
	c_addr.sin_family = AF_INET;
	c_addr.sin_port = htons(atoi(argv[2]));
	inet_aton(argv[1],&c_addr.sin_addr);

	if(connect(c_fd,(struct sockaddr *)&c_addr,c_len) != 0){
    
    
	 perror("connect");
	 exit(-1);
	}
	printf("connect success\n");
	
	while(1){
    
    
		memset(&msg,'\0',sizeof(struct Msg));
		if(mark == 0){
    
    
			printf(">");
		}
		gets(msg.cmd);
		if(strlen(msg.cmd) == 0){
    
    
	        if(mark == 1){
    
    
            	printf(">");
	        }
	        continue;
		}
		mark = 1;
		cmd_Handler(c_fd,msg);
		
		if(fork() == 0){
    
    
			getData(c_fd,msg);
		}
	}
	close(c_fd);
	return 0;
}

//返回文件/文件夹名称
char *getDir(char *cmd)
{
    
    
	char *p;
	p = strtok(cmd," ");
	p = strtok(NULL," ");
	return p;
}

//分析指令
int analyse_Cmd(char *cmd)
{
    
    
	if(!strcmp(cmd,"quit"))         return QUIT;
	if(!strcmp(cmd,"ls"))           return LS;
	if(!strcmp(cmd,"pwd"))          return PWD;
	if(!strcmp(cmd,"lls"))          return LLS;
	if(!strcmp(cmd,"lpwd"))         return LPWD;
	
	if(strstr(cmd,"lcd"))           return LCD;
	if(strstr(cmd,"cd"))            return CD;
	if(strstr(cmd,"upload"))        return UPLOAD;
	if(strstr(cmd,"download"))      return DOWNLOAD;
	return -1;
}

//处理指令内容
void cmd_Handler(int c_fd,struct Msg msg)
{
    
    
	int fd;
	int ret;
	char *pret;
	char readBuf[1024] = {
    
    0};
	ret = analyse_Cmd(msg.cmd);
switch(ret)
{
    
    
	case LS:
	case PWD:
	case CD:
	case DOWNLOAD:
	    write(c_fd,&msg,sizeof(struct Msg));
	    break;
	case LLS:
	    system("ls");
	    break;
	case LPWD:
	    system("pwd");
	    break;

	case LCD:
		pret = getDir(msg.cmd);
		if(chdir(pret) == -1){
    
    
			perror("chdir");
		}
		fd = chdir(pret);
		printf("dir: %s\n",pret);
		getcwd(msg.data,sizeof(msg.data));
		printf("change client dir:%s\n",msg.data);
		break;
	case UPLOAD:
		strcpy(msg.filename,msg.cmd);
		pret = getDir(msg.cmd);
		if(open(pret,O_RDWR) < 0){
    
    
			printf("no file\n");
		}else{
    
    
			fd = open(pret,O_RDWR);
			 read(fd,msg.data,sizeof(msg.data));
			 close(fd);
			 write(c_fd,&msg,sizeof(struct Msg));
			 printf("upload success\n");
		}
		break;
	
	case QUIT:
		write(c_fd,&msg,sizeof(struct Msg));
		exit(1);
		default:
		write(c_fd,&msg,sizeof(struct Msg));
	break;
	}
}

//
void getData(int c_fd,struct Msg msg)
{
    
    
	int fd;
	char *pret;
	
	memset(msg.data,'\0',sizeof(msg.data));
	read(c_fd,&msg,sizeof(struct Msg));
	if(msg.data == NULL){
    
    
		printf("no msg\n");
	}
	else if(msg.type == DOWNLOAD){
    
    
		pret = getDir(msg.filename);
		fd = open(pret,O_RDWR|O_CREAT,0600);
		write(fd,msg.data,strlen(msg.data)+8);
		printf("download success\n");
		close(fd);
	}
	else{
    
    
		printf("<---------------------->\n");
		printf("\n%s\n",msg.data);
		printf("<---------------------->\n");
	}
}


3、代码实现效果

第一步:客户端和服务端建立连接
在这里插入图片描述
第二步:客户端发送lls指令查看当前路径下所有本地文件,发送ls指令查看服务端当前路径下的文件
在这里插入图片描述
第三步:客户端发送uoload aa.txt上传本地文件aa.txt至服务端,发送download config.txt下载服务端config.txt文件到客户端
在这里插入图片描述
第四步:查看上传和下载的文件
在这里插入图片描述
第五步:客户端发送pwd指令查看服务端文件当前所在路径,然后cd …进入上层文件夹,然后cd xx回到当前文件夹
在这里插入图片描述
第六步:客户端发送指令查看本地文件夹所在目录,然后cd FTP进入FTP文件夹,cd …回到当前文件夹
在这里插入图片描述
第七步:客户端发送quit指令退出连接
在这里插入图片描述

4、总结:

在数据交互之前,要记得使用memset清空上一次客户端和服务端数据交换的内容。另外,除了自己写的简单指令外,还可以使用Linux自带的指令功能:输入ftp 127.0.0.1之后在输入乌班图的账号密码,用ftp+指令(如ftp ls)就可以使用相关指令了。
至此,基于Socket套接字实现简单FTP功能项目基本完成,如遇不足,烦请交流指正。