FTP服务器用到的是Socket网络通信,当收到客户端接入的时候,服务器创建子进程对接连接,子进程启动后分析来自客户端的指令,服务端可同时处理多个客户端接入并对指令作出解析,并把执行结果返回给客户端。比如:收到get file1的指令,是客户端想要获取file1文件的,我先用strstr()函数进行字符串分割,获取到文件名,在判断文件是否存在,如果文件存在,就读取文件內容,再将內容通过套接字发给客户端,客户端收到数据后,创建文件,并将收到的数据写入文件,完成文件的远程下载。 (说明网络编程,字符串编程,文件编程)
上传文件和下载文件类似,主要还是涉及文件的操作,字符串的操作,以及网络编程。
还支持了Is、pwd、cd等Linux系统常用的指令。普通指令的实现用popen来调用系统指令,并读取执行的结构。如果不需要获取执行结果,用system函数调用就可以了。 (说明popen,system的编程)
int access(const char *pathname, int mode);
功能:判断文件是否存在
参数1:
参数2:(这里用F_OK)
返回值:
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream); //打开后要用 pclose 关闭文件
功能:可以像打开文件一样打开 shell 指令。后续可以使用 fread 读取内容到缓冲区 buf
参数1:
参数2:
注意:
int chdir(const char *path);
功能:跳转至以参数path 指定目录
参数:
返回值执:
注意:
char *strtok(char *str, const char *delim);
功能:字符串分隔,把参数二的字符修改为’\0’,返回其前面的字符串地址。
参数1:
参数2:
#include
#include int main()
{char buf[128] = "hello Linux !!!";char *p;printf("buf = %s\n", buf);p = strtok(buf, " ");printf("\n第一次处理: 相当于 buf = hello\\0Linux !!! \n");printf("p1 = %s\n", p);printf("buf = %s\n", buf);p = strtok(NULL, " ");printf("\n第二次处理:相当于 buf = hello\\0Linux\\0!!! \n");printf("p2 = %s\n", p);printf("buf = %s\n", buf);printf("buf + 6 = %s\n", buf + 6);p = strtok(NULL, " ");printf("\n第三次处理:从第一个 ! 开始,直到最后遇到\\0结束\n");printf("p3 = %s\n", p);printf("buf = %s\n", buf);printf("buf + 12 = %s\n", buf + 12);p = strtok(NULL, " ");printf("\n第四次处理:后面没有字符串,指针指向 NULL \n");printf("p4 = %s\n", p);printf("buf = %s\n", buf);printf("buf + 15 = %s\n", buf + 15);return 0;
}
输出结果

int strcmp(const char *s1, const char *s2);int strncmp(const char *s1, const char *s2, size_t n);
功能:字符串比较。strncmp能够精确判断字符的个数
参数1:
参数2:
参数3:
返回值:
注意:
参考博文:linux system函数是否执行成功判断方法
#define LS 0
#define LLS 1
#define PWD 2
#define CD 3
#define RM 5
#define GET 6
#define PUT 7
#define LPWD 8
#define LCD 9
#define LRM 10#define QUIT 120struct myFTP
{int set; // 客户端返回给服务器的宏命令int mark; // 判断文件是否存在的标识char cmd[128]; // 用户输入的命令char data[1024]; // 存放根据指令进行相关读取操作的 结果
};
#include "ftp.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include char *stringSplit(char *buf)
{char *p;p = (char *)malloc(128);//buf.cmd空间为128字节p = strtok(buf, " ");p = strtok(NULL, " ");return p;
}/* 判断输入的完整指令的前几个关键指令 */
int getCommandSet(char *cmd)
{if (strncmp(cmd, "ls", 2) == 0)return LS;if (strncmp(cmd, "lls", 3) == 0)return LLS;if (strncmp(cmd, "pwd", 3) == 0)return PWD;if (strncmp(cmd, "cd", 2) == 0)return CD;if (strncmp(cmd, "rm", 2) == 0)return RM;if (strncmp(cmd, "get", 3) == 0)return GET;if (strncmp(cmd, "put", 3) == 0)return PUT;if (strncmp(cmd, "lpwd", 4) == 0)return LPWD;if (strncmp(cmd, "lcd", 3) == 0)return LCD;if (strncmp(cmd, "lrm", 3) == 0)return LRM;if (strncmp(cmd, "quit", 4) == 0)return QUIT;return -1;
}/* 读取服务器处理完后 buf.data 的内容 */
void readFromServer(int c_fd, struct myFTP buf)
{int nread = read(c_fd, &buf, sizeof(buf));if (nread == -1){perror("read");}else if (nread == 0){printf("server quit\n");exit(0);}else{printf("%s", buf.data);}
}void sendCommand(int c_fd, struct myFTP buf)
{buf.mark = 0; //判断文件是否存在的标识char *p_tmp = NULL;int fd;off_t fileSize;while (1){memset(&buf, 0, sizeof(buf)); // 每次操作命令前都先把 buf 的内容清空,确保不会被上次操作遗留的内容影响下次操作printf("\n************************************************************************\n");printf("*****please input (ls pwd cd rm get put quit lls lpwd lcd lrm lcd)*****\n");printf("**************************************************************************\n");printf(">> ");gets(buf.cmd); // 从键盘获取完整命令printf("command:%s\n", buf.cmd);buf.set = getCommandSet(buf.cmd);switch (buf.set){case LS: // 和 PWD 的处理方式一样,所以不需要 breakcase PWD:write(c_fd, &buf, sizeof(buf)); // 把 buf 结构体发送至 c_fd 给服务器处理readFromServer(c_fd, buf);break;case LLS: // 和 LPWD、LRM 的处理方式一样,所以不需要 breakcase LPWD:case LRM:p_tmp = buf.cmd;p_tmp++; // 加一是为了指向第二个字符,以第二个字符开始,屏蔽Lsystem(p_tmp);break;case LCD:p_tmp = stringSplit(buf.cmd); // 字符串分割提取路径strcpy(buf.cmd, p_tmp);int ret = chdir(buf.cmd); // 切换路径if (ret == -1){perror("chdir");}else{printf("chdir success\n");}break;case CD:p_tmp = stringSplit(buf.cmd); // 字符串分割提取路径strcpy(buf.cmd, p_tmp); // 把路径复制到 buf.cmdwrite(c_fd, &buf, sizeof(buf)); // 传整个结构体过去 c_fd 给 serverreadFromServer(c_fd, buf); // 读取服务器操作完返回的数据break;case RM:write(c_fd, &buf, sizeof(buf)); // 把 rm xxx 传给客户端用 system 函数处理readFromServer(c_fd, buf);break;case GET:p_tmp = stringSplit(buf.cmd); // 提取文件名strcpy(buf.cmd, p_tmp);write(c_fd, &buf, sizeof(buf));read(c_fd, &buf, sizeof(buf)); // 把服务器传送过来 c_fd 处理完的内容读取到 buf 结构体if (buf.mark == 0) // 判断服务器是否因为找不到目标文件而把标志位设置为 -1{fd = open(buf.cmd, O_RDWR | O_CREAT, 0777); // 打开 get 的文件,如果没有则创建文件,权限可读可写write(fd, buf.data, strlen(buf.data)); // 把客户端放进 buf.data 的内容写入 fd 文件close(fd);printf("get success\n");}else // 服务器因为找不到目标文件而把标志位设置为 -1{printf("%s\n", buf.data);}break;case PUT:p_tmp = stringSplit(buf.cmd); // 提取文件名strcpy(buf.cmd, p_tmp);if (access(buf.cmd, F_OK) == -1) // 如果找不到目标文件{buf.mark = -1; // 把标志位至 -1printf("NO this file\n");strcpy(buf.data, "NO this file\n");write(c_fd, &buf, sizeof(buf));}else{fd = open(buf.cmd, O_RDWR); // 打开目标文件,权限可读可写fileSize = lseek(fd, 0, SEEK_END); // 移动光标至文件最后,返回值是该文件(光标前面)的字节数lseek(fd, 0, SEEK_SET); // 设置光标至最前read(fd, buf.data, fileSize); // 把目标文件的内容读取至 buf.datawrite(c_fd, &buf, sizeof(buf)); // 把 buf 结构体发送至 c_fd 传给服务器close(fd); // 关闭目标文件printf("put success\n");}break;case QUIT:exit(0);break;case -1:printf("error command\n");break;}}
}int main(int argc, char *argv[])
{int c_fd;int c_Ret;struct myFTP buf;struct sockaddr_in c_addr;memset(&c_addr, 0, sizeof(struct sockaddr_in));if (argc != 3){printf("input error\n");}// 1. socketc_fd = socket(AF_INET, SOCK_STREAM, 0);// 防止段错误if (c_fd == -1){perror("socket");exit(-1);}// 2. bind 配置 struct sockaddr_in 结构体,绑定时再转换成 struct sockaddr * 结构体类型c_addr.sin_family = AF_INET; /* address family: AF_INET */c_addr.sin_port = htons(atoi(argv[2])); /* port in network byte order */inet_aton(argv[1], &c_addr.sin_addr); /* internet address */// 3. connectc_Ret = connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr_in));// 防止段错误if (c_Ret < 0){perror("connect");exit(-1);}else{printf("connect success\n");sendCommand(c_fd, buf);}close(c_fd);return 0;
}
#include "ftp.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include //判断一个 system 函数调用 shell 脚本是否正常结束
int System_Check(int result)
{if ((-1 != result) && (WIFEXITED(result)) && (!(WEXITSTATUS(result))))return 0;elsereturn -1;
}void commandHandler(int c_fd, struct myFTP buf, int cmd)
{int nread;int result = 0;int fd;off_t fileSize; // 文件内容大小buf.mark = 0; // 0 -> 成功, -1 -> 失败int ret;FILE *file;/* 连接到客户端后一直 while(1) 读取客户端发过来的内容进行处理 */while (1){memset(&buf, 0, sizeof(buf));nread = read(c_fd, &buf, sizeof(buf));if (nread < 0){perror("read");}else if (nread == 0){printf("No.%d client quit\n", cmd);exit(0);}else{printf("No.%d command:> %s\n\n", cmd, buf.cmd);switch (buf.set){case LS:case PWD:file = popen(buf.cmd, "r"); // popen()可以执行shell命令,并读取此命令的返回值fread(buf.data, 1024, 1, file);write(c_fd, &buf, sizeof(buf));pclose(file);break;case CD:ret = chdir(buf.cmd);if (ret == -1){perror("chdir");}else{strcpy(buf.data, "chdir success! You can input ls to check!~\n");write(c_fd, &buf, sizeof(buf));}break;case RM:result = system(buf.cmd); // system 函数处理命令 rm xxxif (!System_Check(result)){strcpy(buf.data, "rm success! You can input ls to check!~\n");write(c_fd, &buf, sizeof(buf));}else{strcpy(buf.data, "rm fail\n");write(c_fd, &buf, sizeof(buf));}break;case GET:if (access(buf.cmd, F_OK) == -1) // 如果找不到目标文件{buf.mark = -1; // 把标志位至 -1strcpy(buf.data, "NO this file\n");write(c_fd, &buf, sizeof(buf));}else{fd = open(buf.cmd, O_RDWR); // 打开目标文件,权限可读可写fileSize = lseek(fd, 0, SEEK_END); // 移动光标至文件最后,返回值是该文件(光标前面)的字节数lseek(fd, 0, SEEK_SET); // 设置光标至最前read(fd, buf.data, fileSize); // 把目标文件的内容读取至 buf.datawrite(c_fd, &buf, sizeof(buf)); // 把 buf 结构体发送至 c_fd 传给客户端close(fd); // 关闭目标文件printf("client command: GET success!~\n");}break;case PUT:if (buf.mark == 0) // 判断客户端是否因为找不到目标文件而把标志位设置为 -1{fd = open(buf.cmd, O_RDWR | O_CREAT, 0777);write(fd, buf.data, strlen(buf.data));close(fd);}else // 客户端因为找不到目标文件而把标志位设置为 -1{printf("%s\n", buf.data);}break;}}}
}int main(int argc, char *argv[])
{int s_fd;int c_fd;int s_Ret;struct myFTP buf;int cmd = 0;struct sockaddr_in s_addr;struct sockaddr_in c_addr;memset(&s_addr, 0, sizeof(struct sockaddr_in));memset(&c_addr, 0, sizeof(struct sockaddr_in));if (argc != 3){printf("input error\n");}// 1. sockets_fd = socket(AF_INET, SOCK_STREAM, 0);// 防止段错误if (s_fd == -1){perror("socket");exit(-1);}// 2. bind 配置 struct sockaddr_in 结构体,绑定时再转换成 struct sockaddr * 结构体类型s_addr.sin_family = AF_INET; /* address family: AF_INET */s_addr.sin_port = htons(atoi(argv[2])); /* port in network byte order */inet_aton(argv[1], &s_addr.sin_addr); /* internet address */s_Ret = bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));// 防止段错误if (s_Ret == -1){perror("bind");exit(-1);}// 3. listens_Ret = listen(s_fd, 10);// 防止段错误if (s_Ret == -1){perror("listen");exit(-1);}// 4. acceptint len = sizeof(struct sockaddr_in);while (1){c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &len);if (c_fd == -1){perror("accept");exit(-1);}else{cmd++;printf("get connect: No.%d IP:%s\n", cmd, inet_ntoa(c_addr.sin_addr));}if (fork() == 0){commandHandler(c_fd, buf, cmd);}}close(s_fd);close(c_fd);return 0;
}
