FTP服务器用到的是Socket网络通信,当收到客户端接入的时候,服务器创建子进程对接连接,子进程启动后分析来自客户端的指令,服务端可同时处理多个客户端接入并对指令作出解析,并把执行结果返回给客户端。比如:收到get file1的指令,是客户端想要获取file1文件的,我先用strstr()函数进行字符串分割,获取到文件名,在判断文件是否存在,如果文件存在,就读取文件內容,再将內容通过套接字发给客户端,客户端收到数据后,创建文件,并将收到的数据写入文件,完成文件的远程下载。
上传文件和下载文件类似,主要还是涉及文件的操作,字符串的操作,以及网络编程。
还支持了Is、pwd、cd等Linux系统常用的指令。普通指令的实现用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 120
struct 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 的处理方式一样,所以不需要 break
case PWD:
write(c_fd, &buf, sizeof(buf)); // 把 buf 结构体发送至 c_fd 给服务器处理
readFromServer(c_fd, buf);
break;
case LLS: // 和 LPWD、LRM 的处理方式一样,所以不需要 break
case LPWD:
case LRM:
p_tmp = buf.cmd;
p_tmp++; // 加一是为了指向第二个字符,以第二个字符开始,屏蔽L
system(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.cmd
write(c_fd, &buf, sizeof(buf)); // 传整个结构体过去 c_fd 给 server
readFromServer(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; // 把标志位至 -1
printf("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.data
write(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. socket
c_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. connect
c_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;
else
return -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 xxx
if (!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; // 把标志位至 -1
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.data
write(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. socket
s_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. listen
s_Ret = listen(s_fd, 10);
// 防止段错误
if (s_Ret == -1)
{
perror("listen");
exit(-1);
}
// 4. accept
int 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;
}
执行校验:
md5sum test.txt
#注1:结果为1行2列。第一列是md5值,第二列是md5值对应的文件名