ftp服务器使用说明

  1. 运行服务端
    ./server 本机IP地址 端口号(使用ifconfig查看本机IP 地址)
  2. 运行客户端 ./client 服务器IP地址 相同端口号

运行成功后可执行以下指令,实现类似云盘功能

指令说明

  1. ls —— 查看服务端当下目录
  2. lls —— 查看客户端当下目录
  3. pwd —— 查看服务端当前路径
  4. quit —— 客户端退出连接
  5. cd name —— 令服务器进入到name路径下下
  6. get name —— 从服务器上下载name文件
  7. put name ——将客户端name文件上传到服务器

一、头文件defin.h——存储指令及数据

#define        ls          1
#define        pwd         2
#define        cd          3
#define        get         4
#define        put         5
#define        lls         6
#define        quit        7

struct Msg
{
	char com[100];//存储指令
	char buf[1024];//存储数据
};

二、服务器server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "defin.h"
#include "sys/types.h"
#include <unistd.h>
#include "sys/socket.h"
#include <arpa/inet.h>

void ErrorReason(char *err,int count,int Errnum);
int Msg_Process(int SonId,char *Cmd);
int JudgeCmd(char *Cmd);
char *MsgPerform(int SonId,int Cmd,struct Msg *msg);
char *comcut(struct Msg *msg);

int count=0;
int main(int argc,char **argv)
{
	if(argc!=3)
	{
		printf("error argc\n");
		exit(-1);
	}
	int cp1;//用于辅助程序算法而设的标志位

	int soc=socket(AF_INET,SOCK_STREAM,0);//创建套接字
	ErrorReason("socket",soc,-1);

	struct sockaddr_in soc_init;
	struct in_addr soc_initaddr;
	memset(&soc_init,0,sizeof(struct sockaddr_in));
	memset(&soc_initaddr,0,sizeof(struct in_addr));	


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

	cp1=bind(soc,(struct sockaddr *)&soc_init,sizeof(struct sockaddr));//把地址和端口号组合赋给socket
	ErrorReason("bind",cp1,-1);

	cp1=listen(soc,10);//监听连接请求,最大连接数10
	ErrorReason("listen",cp1,-1);
	
	struct sockaddr_in soc_on;
	int len=sizeof(struct sockaddr_in);
	int acc;
	pid_t id;

	struct Msg strdata;//用于存储指令和数据的结构体
	int sizlen;//指针,文件等的长度
	int strcmd;//存储宏定义后的指令
	char *pdata=NULL;//存储指令执行后的数据
	memset(&strdata,0,sizeof(struct Msg));
	while(1)
	{
		count++;//用于区分客户端而设的标志位
		acc=accept(soc,(struct sockaddr *)&soc_on,(socklen_t *)&len);//等待客户端接入
		ErrorReason("accept",acc,-1);
	
		id=fork();//创建子进程与客户端完成对接
		ErrorReason("fork",id,-1);
		if(id==0)//创建子进程与客户端完成对接
		{
			while(1)
			{
				memset(strdata.com,'\0',sizeof(strdata.com));
				sizlen=recv(acc,(void *)(strdata.com),sizeof(strdata.com),0);//阻塞直到客户端向服务端发送数据
				if(sizlen>0)//数据正常处理
				{
					strcmd=Msg_Process(acc,strdata.com);//返回可执行的命令
					if(strcmd==0)//如果命令有问题,则进入
					{
						send(acc,(void *)"error cmd",strlen("error cmd"),0);
						continue;
					}
					printf("1:%s\n",strdata.com);
					pdata=MsgPerform(acc,strcmd,&strdata);//处理命令,返回命令执行后的数据
					printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
					if(strcmp(strdata.com,"lls"))
					{
						len=send(acc,(void *)pdata,strlen(pdata),0);//向客户端发送数据
					}
				}else
				if(sizlen==0)//数据有问题处理
				{
					printf("recv\n");
					break;
				}
			}
		}
	}
	
	close(soc);
	close(acc);

	return 0;
}

int Msg_Process(int SonId,char *Cmd)//处理客服端发送的命令,返回优化后的命令
{
	char *strcmd=Cmd;
	int runcmd;
	runcmd=JudgeCmd(strcmd);//返回优化后的指令
	return runcmd;
}

int JudgeCmd(char *Cmd)//优化命令
{
	char *p=NULL;

	if(!strcmp(Cmd,"ls")) return ls;else
	if(!strcmp(Cmd,"pwd")) return pwd;else
	if(strstr(Cmd,"cd")!=NULL) return cd;else
	if(strstr(Cmd,"get")!=NULL) return get;else
	if(strstr(Cmd,"put")!=NULL) return put;else
	if(strcmp(Cmd,"lls")==0) return lls;else
	if(strcmp(Cmd,"quit")==0) return quit;else 
	return 0;
}

char *MsgPerform(int SonId,int Cmd,struct Msg *msg)//执行命令,返回命令结果
{
	struct Msg *macom=msg;
	long len;//指针,文件等的长度
	int cp1;//用于辅助程序算法而设的标志位

	char *str=NULL;//用于返回可执行的命令(字符串)
	char str1[100]={'\0'};//用于返回可执行的命令(字符串)
	char data[1024]={'\0'};
	char *retudata=NULL;//执行命令后的数据存储区

	FILE *fp=NULL;//打开文件用的文件描述符
	
	switch(Cmd)
	{
		case ls:
		case pwd://执行服务端shll指令
			{
				if(!strcmp((macom->com),"ls"))
				{
					fp=popen("/bin/ls","r");//建立管道IO
				}else
				if(!strcmp((macom->com),"pwd"))
				{
					fp=popen("/bin/pwd","r");//建立管道IO
				}
				if(fp==NULL)
				{
					perror("popen");
					return "popen error";
				}
				fread(data,sizeof(char),sizeof(data),fp);//读执行shll指令得到的数据
				fclose(fp);
				retudata=data;
				len=strlen(retudata);
				return retudata;//返回执行指令得到的数据
			}break;
		case cd://服务端跳转文件夹
			{
				strcpy(str1,comcut(macom));//分割macom->com字符串,返回可执行命令
	
				cp1=chdir(str1);//进入路径为str的文件夹
				if(cp1==-1)
				{
					return "error cmd";
				}
				getcwd(data,sizeof(data));//把当前目录赋值给data
				retudata=data;
				len=strlen(retudata);
				return retudata;//返回当前目录(字符串)
			}break;
		case get://从服务器上下载文件到客户端
			{
				strcpy(str1,comcut(macom));//分割macom->com字符串,返回可执行命令
				fp=fopen(str1,"r");//打开str文件
				if(fp==NULL)
				{
					return "error cmd";
				}
				fseek(fp,0,SEEK_END);//把光标移动到文件流尾
				len=ftell(fp);//取得文件流光标所在位置,读取文件大小
				fseek(fp,0,SEEK_SET);//把光标移动到文件流首
				len=fread((void *)data,sizeof(char),len,fp);//读取文件内容
				retudata=data;
				fclose(fp);
				return retudata;//返回读取到的文件数据
			}break;
		case put://将客户端文件上传到服务器
			{
				memset(macom->buf,'\0',sizeof(macom->buf));
				recv(SonId,(void *)(macom->buf),sizeof(macom->buf),0);

				if(!(strcmp(macom->buf,"error")))//进入数据错误处理
                                {
                                        return "error cmd";
                                }

				strcpy(str1,(macom->com)+4);//拷贝com指令后面的文件名(字符串)
                                fp=fopen(str1,"w+");//创建文件str1
                                if(fp==NULL)
                                {
                                        return "error cmd";
                                }
				len=strlen(macom->buf);
                                fwrite((void *)(macom->buf),sizeof(char),len,fp);//写入数据到文件
                                fclose(fp);

				return "put OK";
			}break;
		case lls://查看客户端当下目录
			{
				return "lls";
			}break;
		case quit://客户端退出(子进程退出)
			{
				printf("%dProcess exits\n",count);//服务端输出提示哪个进程退出,count为标志位
				exit(3);//子进程退出
			}break;
		default:return "error com";
	}
}

char *comcut(struct Msg *msg)//分割字符串,后处理字符串
{
	int i=0;
	int j=0;//用于辅助程序算法而设的标志位
	char *p=NULL;//处理分割字符串用的辅助位
	char *comaddr[10]={"NULL"};//存储分割字符串后的数据而设的指针数组
	char str[100]={'\0'};
	char str1[300]={'\0'};//存储命令处理后的数据

	p=strtok(msg->com," ");//分割字符串
	while(p!=NULL)//处理分割字符串
	{
		comaddr[i]=p;
		p=strtok(NULL," ");
		i++;
	}
		for(j=0;j<i-1;j++)//重修拼合指针数据的字符串
		{
			if(!strcmp(comaddr[0],"cd"))
			{
				sprintf(str,"/%s",comaddr[j+1]);
			}else
			if(!(strcmp(comaddr[0],"get")))
			{
				sprintf(str,"%s",comaddr[j+1]);
			}
			strcpy(str1,strcat(str1,str));
		}
	p=str1;
	return p;
}

void ErrorReason(char *err,int count,int Errnum)//处理错误函数
{
	if(count==Errnum)
	{
		perror(err);
		exit(-1);
	}
	return;
}

客户端client.c

#include <stdio.h>
#include "defin.h"
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

void ErrorReason(char *err,int count,int Errnum);
int Msg_Process(int SonId,struct Msg *Cmd);
int JudgeCmd(struct Msg *Cmd);
struct Msg *PutCom(int SonId,int Cmd,struct Msg *msg);
struct Msg *GetBuf(int SonId,int Cmd,struct Msg *msg);

int main(int argc,char **argv)
{
	if(argc!=3)
	{
		printf("error argc\n");
		exit(-1);
	}
	int cp1;

	int soc=socket(AF_INET,SOCK_STREAM,0);//创建套接字
	ErrorReason("socket",soc,-1);

	struct sockaddr_in soc_in;
	struct in_addr in_a;
	memset(&soc_in,0,sizeof(struct sockaddr_in));
        memset(&in_a,0,sizeof(struct in_addr)); 

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

	cp1=connect(soc,(struct sockaddr *)&soc_in,sizeof(struct sockaddr_in));//发起连接指定的网络地址
	ErrorReason("connect",cp1,-1);

	struct Msg strdata;
	struct Msg *getdata=(struct Msg *)malloc(sizeof(struct Msg));//用于存储服务端发送的数据
	int len;//用于计算指针、字符串长度等
	int strcmd;

	while(1)
	{
		memset(strdata.com,'\0',sizeof(strdata.com));
		fgets(strdata.com,sizeof(strdata.com),stdin);//获取命令
		len=strlen(strdata.com);
		*(strdata.com+len-1)='\0';

		strcmd=Msg_Process(soc,&strdata);//返回可执行的命令
		
		if(strcmd==0)//如果命令有问题,则进入
		{
			printf("error cmd\n");
			continue;
		}
		PutCom(soc,strcmd,&strdata);//向服务端发送命令
		memset(getdata->buf,'\0',sizeof(getdata->buf));
		getdata=GetBuf(soc,strcmd,&strdata);//返回命名执行后的数据(返回服务端发送过来的的数据)
		printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
		printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
		if(strcmd==lls)
		{
			system("/bin/ls");
		}else
		{
			printf("main:%s\n",getdata->buf);
		}
		printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
		printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
	}
	
	close(soc);

	return 0;
}

int Msg_Process(int SonId,struct Msg *Cmd)//返回可执行的命令
{
        struct Msg *p=Cmd;
        int runcmd;
        runcmd=JudgeCmd(p);
        return runcmd;
}

int JudgeCmd(struct Msg *Cmd)//优化命令
{
	struct Msg *p=Cmd;
        if(!strcmp(p->com,"ls"))       return ls;  else
        if(!strcmp(p->com,"pwd"))      return pwd; else
        if(strstr(p->com,"cd")!=NULL)  return cd;  else
        if(strstr(p->com,"get")!=NULL) return get; else
        if(strstr(p->com,"put")!=NULL) return put; else
        if(!strcmp(p->com,"lls"))      return lls; else
        if(!strcmp(p->com,"quit"))     return quit;else
        return 0;
}

struct Msg *PutCom(int SonId,int Cmd,struct Msg *msg)//向服务端发送命令
{
	struct Msg *p=msg;
	send(SonId,(void *)p->com,strlen(p->com),0);//向服务端发送命令
	return p;
}

struct Msg *GetBuf(int SonId,int Cmd,struct Msg *msg)//返回命名执行后的数据(返回服务端发送过来的的数据)
{
	FILE *fd=NULL;//用于打开文件用的文件描述符
	char str[50]={'\0'};//用于存储继续处理后的命令
	int len;//用于计算长度
	struct Msg *getdata=msg;//用于存储服务端发送回的数据
	memset(getdata->buf,0,sizeof(getdata->buf));

	switch(Cmd)//进入相关指令进行工作
	{
		case ls:
		case pwd://执行服务端shll指令
			{
				recv(SonId,(void *)getdata->buf,sizeof(getdata->buf),0);
				len=strlen(getdata->buf);
			}break;
		case cd://服务端跳转文件夹
			{
				recv(SonId,(void *)getdata->buf,sizeof(getdata->buf),0);
                                len=strlen(getdata->buf);
			}break;
		case get://从服务器上下载文件到客户端
			{
				recv(SonId,(void *)getdata->buf,sizeof(getdata->buf),0);
				len=strlen(getdata->buf);
				if(!strcmp(getdata->buf,"error cmd"))
				{
					break;
				}
				len=strlen(getdata->buf);
				strcpy(str,(getdata->com)+4);
				fd=fopen(str,"w+");
				if(fd==NULL)
				{
					perror("fopen error");
					break;		
				}
				fwrite((void *)(getdata->buf),sizeof(char),len,fd);
				fclose(fd);
				memset(getdata->buf,'\0',sizeof(getdata->buf));
				strcpy(getdata->buf,"get OK");
			}break;
		case put://将客户端文件上传到服务器
			{
				strcpy(str,(getdata->com)+4);
				len=strlen(str);
			
				fd=fopen(str,"r");
				if(fd==NULL)
				{
					usleep(100);
					send(SonId,(void *)"error",strlen("error"),0);

					recv(SonId,(void *)getdata->buf,sizeof(getdata->buf),0);
					return getdata;
				}

				fseek(fd,0,SEEK_END);//将光标移动到文件流尾处
                                len=ftell(fd);//取得文件流光标所在位置
                                fseek(fd,0,SEEK_SET);
                                fread(getdata->buf,sizeof(char),len,fd);
				len=strlen(getdata->buf);
		
				send(SonId,(void *)getdata->buf,len,0);
	
				fclose(fd);

				memset(getdata->buf,'\0',sizeof(getdata->buf));
				usleep(100);
				recv(SonId,(void *)getdata->buf,sizeof(getdata->buf),0);
				memset(getdata->buf,'\0',sizeof(getdata->buf));
				strcpy(getdata->buf,"put OK");
			}break;
		case lls://查看客户端当下目录
			{
				strcpy(getdata->buf,"/bin/ls");
			}break;
		case quit://客户端退出(子进程退出)
			{
				strcpy(getdata->buf,"quit");
			}break;
	}
	return getdata;
}



void ErrorReason(char *err,int count,int Errnum)//处理错误函数
{
        if(count==Errnum)
        {
                perror(err);
                exit(-1);
        }
        return;
}

运行结果:运行服务端成功后,可接入多个客户端

读者可亲自实验ftp服务器开发的乐趣