一、项目概述
1.该项目是基于Linux网络编程实现的FTP服务器,服务器由服务端和客户端组成,具有浏览远程服务端的文件和浏览客户端本地文件,同时支持客户端对远程服务端文件的上传和下载。
2.ftp服务器用到的是Socket通信,当收到客户端接入的时候,创建子进程对接连接,子进程启动后分析来自客户端的指令,比如收到get file1的指令,是客户端想要获取file1文件的,先用strstr函数进行字符串分割,获取到文件名,在判断文件是否存在,如果文件存在,就读取文件內容,再将內容通过套接字发给客户端,客户端收到数据后,创建文件,并将收到的数据写入文件,完成文件的远程下载。上传文件和下载文件类似,主要还是涉及文件的操作,字符串的操作,以及网络编程。
3.还支持了Is、pwd、cd等Linux系统常用的指令。普通指令的实现用popen来调用系统质量,并读取执行的结构。如果不需要获取执行结果,用system函数调用就可以。
- ls———查看服务端文件
- lls———查看客户端自己的文件
- cd———切换服务端目录
- lcd———切换客户端自己的目录
- put———上传文件
- get———下载文件
- pwd———显示路径
- quit———退出
二、编程实现
源码:https://gitee.com/GeekerGao/ftp-network-disk
config.h
// 定义常量
#define LS 0 // 常量 LS,表示列出目录内容
#define GET 1 // 常量 GET,表示从服务端获取文件到客户端
#define PWD 2 // 常量 PWD,表示显示当前工作目录
#define IFGO 3 // 常量 IFGO,用于判断命令是否需要进一步处理
#define LCD 4 // 常量 LCD,表示切换本地工作目录
#define LLS 5 // 常量 LLS,表示在本地执行 ls 命令
#define CD 6 // 常量 CD,表示切换服务器工作目录
#define PUT 7 // 常量 PUT,表示从客户端上传文件到服务端
#define QUIT 8 // 常量 QUIT,表示退出客户端或服务器
#define DOFILE 9 // 常量 DOFILE,表示传输文件内容
// 定义消息结构体 Msg
struct Msg
{
int type; // 消息类型,表示不同的命令或操作
char data[1024]; // 存储消息数据,如命令、消息内容等
char secondBuf[128]; // 存储消息中的第二部分数据,如文件内容等
};
服务端
server.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include <sys/stat.h>
#include <fcntl.h>
int get_cmd_type(char *cmd) // 根据命令字符串获取命令类型的整数值
{
if(!strcmp("ls",cmd)) return LS;
if(!strcmp("quit",cmd)) return QUIT;
if(!strcmp("pwd",cmd)) return PWD;
if(strstr(cmd,"cd")!=NULL) return CD;
if(strstr(cmd,"get")!=NULL) return GET;
if(strstr(cmd,"put")!=NULL) return PUT;
return 100;
}
char *getDesDir(char *cmsg)// 获取目标目录路径
{
char *p;
p = strtok(cmsg," ");
p = strtok(NULL," ");
return p;
}
void msg_handler(struct Msg msg, int fd)// 处理客户端消息
{
char dataBuf[1024]={0};// 用于存储文件内容的缓冲区
char *file = NULL;// 用于存储要操作的文件名
int fdfile;
printf("cmd:%s\n",msg.data);// 打印接收到的命令
int ret = get_cmd_type(msg.data);// 获取命令类型的整数值
switch(ret){
case LS:
case PWD:
msg.type = 0;// 设置消息类型,用于不同的响应
FILE *r = popen(msg.data,"r");// 执行 shell 命令并创建子进程
fread(msg.data,sizeof(msg.data),1,r);// 从子进程读取数据到缓冲区
write(fd, &msg,sizeof(msg)); // 发送数据给客户端
break;
case CD:
msg.type = 1;
char *dir = getDesDir(msg.data);// 获取要切换的目标目录
printf("dir:%s\n",dir);
chdir(dir);// 切换当前工作目录
break;
case GET:
file = getDesDir(msg.data);// 获取要下载的文件名
if(access(file,F_OK) == -1){ //判断文件是否存在
strcpy(msg.data,"No This File!");
write(fd,&msg,sizeof(msg));// 发送消息给客户端
}else{
msg.type = DOFILE;// 设置消息类型为发送文件内容
fdfile = open(file,O_RDWR);// 打开文件以供读取
read(fdfile,dataBuf,sizeof(dataBuf));// 读取文件内容到缓冲区
close(fdfile);//关闭文件
strcpy(msg.data,dataBuf);// 将文件内容复制到消息数据中
write(fd,&msg,sizeof(msg));// 向客户端发送包含文件内容的消息
}
break;
case PUT:
fdfile = open(getDesDir(msg.data),O_RDWR|O_CREAT,0666);//打开/创建文件
write(fdfile, msg.secondBuf, strlen(msg.secondBuf));// 将客户端发送的文件数据写入文件
close(fdfile);//关闭文件
break;
case QUIT:
printf("client quit!\n");// 打印客户端退出信息
exit(-1);// 退出程序
}
}
int main(int argc, char **argv)
{
int s_fd;
int c_fd;
int n_read;
char readBuf[128];
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
struct Msg msg;
if(argc != 3){
printf("param is not good\n");
exit(-1);
}
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));//数据清空
//1. socket
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if(s_fd == -1){
perror("socket");
exit(-1);
}
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],&s_addr.sin_addr);
//2. bind
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3. listen
listen(s_fd,10);
//4. accept
int clen = sizeof(struct sockaddr_in);
while(1){
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
if(c_fd == -1){
perror("accept");
}
printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));
if(fork() == 0){ // 创建子进程处理客户端连接
while(1){
memset(msg.data,0,sizeof(msg.data));//清除数据
n_read = read(c_fd, &msg, sizeof(msg)); // 从客户端读取消息
if(n_read == 0){ //判断读取的字节数
printf("client out\n");// 打印客户端断开连接的信息
break;
}else if(n_read > 0){
msg_handler(msg,c_fd);// 处理客户端消息
}
}
}
}
close(c_fd);//关闭客户端socket
close(s_fd);//关闭服务端socket
return 0;
}
客户端
client.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include "config.h"
char * getdir(char *cmd)//从命令字符串中解析出参数部分,用于获取操作命令所涉及的文件路径
{
char *p;
p = strtok(cmd, " " );
p = strtok(NULL, " ");
return p;
}
int get_cmd_type(char *cmd)//根据输入的命令字符串判断命令类型,返回相应的整数标识
{
if(strstr(cmd,"lcd")) return LCD;
if(!strcmp("quit",cmd)) return QUIT;
if(!strcmp("ls",cmd)) return LS;
if(!strcmp("lls",cmd)) return LLS;
if(!strcmp("pwd",cmd)) return LS;
if(strstr(cmd,"cd")) return CD;
if(strstr(cmd,"get")) return GET;
if(strstr(cmd,"put")) return PUT;
return -1;
}
int cmd_handler(struct Msg msg, int fd)
{
char *dir = NULL;
char buf[32];
int ret;
int filefd;
ret = get_cmd_type(msg.data);// 根据命令类型获取对应的整数标识
switch(ret){
case LS:
case CD:
case PWD:
msg.type = 0;
write(fd,&msg,sizeof(msg));// 向服务器发送消息,请求执行列目录、切换目录、显示当前目录等操作
break;
case GET:
msg.type = 2;
write(fd,&msg,sizeof(msg));//向服务器发送消息,请求获取文件
break;
case PUT:
strcpy(buf,msg.data);
dir = getdir(buf);// 获取上传文件的路径
if(access(dir,F_OK) == -1){ //判读要上传的文件是否存在
printf("%s not exsit\n",dir);
}else{
filefd = open(dir,O_RDWR);//打开文件
read(filefd,msg.secondBuf,sizeof(msg.secondBuf)); // 读取文件内容到消息结构的缓冲区
close(filefd);//关闭文件
write(fd,&msg,sizeof(msg));// 将消息发送给服务器,执行上传文件操作
}
break;
case LLS:
system("ls");// 在客户端执行系统命令,显示本地目录内容
break;
case LCD:
dir = getdir(msg.data);// 获取切换目录的路径
chdir(dir);// 切换本地目录
break;
case QUIT:
strcpy(msg.data,"quit");
write(fd,&msg,sizeof(msg));// 向服务器发送退出消息
close(fd);// 关闭连接
exit(-1);
}
return ret;
}
void handler_server_message(int c_fd, struct Msg msg)//处理从服务器接收到的消息
{
int n_read;
struct Msg msgget;
int newfilefd;
n_read = read(c_fd, &msgget, sizeof(msgget));// 从服务器读取消息响应
if(n_read == 0){
printf("server is out,quit\n");// 如果没有读取到数据,服务器可能已关闭
exit(-1);
}
else if(msgget.type == DOFILE){
char *p = getdir(msg.data);// 获取要保存文件的路径
newfilefd = open(p,O_RDWR|O_CREAT,0600);// 创建本地文件
write(newfilefd,msgget.data,strlen(msgget.data));// 将服务器传来文件数据写本地文件
putchar('>');// 输出命令提示符
fflush(stdout);
}
else{
printf("--------------------------------\n");
printf("\n%s\n",msgget.data);// 打印服务器返回的数据,可能是目录列表等信息
printf("--------------------------------\n");
putchar('>');// 输出命令提示符
fflush(stdout);
}
}
int main(int argc, char **argv)
{
int c_fd;
struct sockaddr_in c_addr;
struct Msg msg;
memset(&c_addr,0,sizeof(struct sockaddr_in));
if(argc != 3){
printf("param is not good\n");
exit(-1);
}
//1. socket
c_fd = socket(AF_INET, SOCK_STREAM, 0);
if(c_fd == -1){
perror("socket");
exit(-1);
}
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],&c_addr.sin_addr);
//2.connect
if(connect(c_fd, (struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){
perror("connect");
exit(-1);
}
printf("connect ...\n");
int mark = 0;// 标记是否需要输出命令提示符
while(1){
memset(msg.data,0,sizeof(msg.data));// 清除消息缓冲区
if(mark == 0) printf(">"); // 输出命令提示符
gets(msg.data);// 获取用户输入的命令
if(strlen(msg.data) == 0){
if(mark == 1){
printf(">");
}
continue;// 用户未输入命令,继续循环
}
mark = 1;// 标记已经有输入的命令
int ret = cmd_handler(msg,c_fd);// 处理用户输入的命令
if(ret>IFGO){
putchar('>');
fflush(stdout);
continue; // 如果命令需要进一步处理,继续循环
}
if(ret==-1){
printf("command not \n"); // 无法识别的命令
printf(">");
fflush(stdout);
continue;// 继续循环等待下一个命令
}
handler_server_message(c_fd, msg);// 处理服务器响应的消息
}
return 0;
}