原创首发于CSDN,转载请注明出处,谢谢!
文章目录
- 模仿 Liunx 系统自身携带FTP服务
- |最简版 FTP 服务的基本步骤(可配合下一节的代码阅读)
- |最简版 FTP 服务的基本命令
- FTP服务功能的代码实现
- |服务端代码 `service.c`
- |客户端代码 `client.c`
- |实现效果展示
- 思绪
- 待解疑惑
- 参考博文
- 文章更新记录
模仿 Liunx 系统自身携带FTP服务
对于初入 L i n u x Linux Linux *** 作系统的新人在学习过 文件IO、进程、进程间通信、线程 等内容对于 Linux 系统具有基本的理解之后,下一步要开始学习 Socket套接字网络编程并以其实现一个最原始的FTP服务。
|最简版 FTP 服务的基本步骤(可配合下一节的代码阅读)FTP , File Transfer Protocol(文件传输协议),互联网上专门用来传输文件的协议。对于支持FTP协议的服务器就是FTP服务器,更多内容请自行百度。
- 服务端运行FTP功能,开辟进程( w h i l e ( ) while() while() & f o r k ( ) fork() fork()),等待客户端的请求;
- 客户端运行FTP命令,申请服务端的服务,如 FTP 127.0.0.1 8989;
- 进程收到客户端的请求后,使用 f o r k ( ) fork() fork() 派生出子进程与客户端进行数据交互,通过套接字 s o c k e t socket socket 管道对文件进行传输;
- 客户端输入命令,服务端接收命令,无错的情况下,彼此建立数数据链接(如使用 T C P TCP TCP 端口 8989 进行具体的数据传输,具体代码见下一节内容);
- 在完成一次或者多次( w h i l e while while)数据传输之后,客户端输入命令 q u i t quit quit 退出,关闭套接字 s_fd 、c_fd ,结束整个进程。
利用 C 语言实现 FTP 功能主要在于服务端与客户端的交互命令的设计与 代码实现 ,大致功能命令如下所示(XX代表文件名):
-
服务端(service):
获取服务端的文件:get xx
展示服务端中的文件:ls xx
进入服务端某个文件夹:cd xx
显示服务端的当前路径:pwd -
客户端(client):
查看客户端本地文件:lls xx
进入客户端里的文件夹:lcd xx
客户端上传文件到服务端:put xx
客户端退出:quit
[客户端强制退出时,服务端 read 为零 显示 Client Quit。]
FTP服务功能的代码实现
出于文本阅读的简洁考虑,笔者将文本代码中的头文件部分全部删去,感兴趣的读者请自行添加头文件调试。另外,对于代码中九个宏定义内容,读者可保留至自定义的头文件 config.h
中,笔者此处不作封装出于当时的代码调试以及现在的博文阅读方便。
service.c
/* service.c */
#define LS 0
#define GET 1
#define PWD 2
#define IFGO 3
#define LCD 4
#define LLS 5
#define CD 6
#define PUT 7
#define QUIT 8
#define DOFILE 9
struct Msg
{
int type;
char data[1024];
char secondBuf[128];
}msg;
int get_cmd_type(char *cmd);
char *getDesDir(char *cmsg);
void msg_handler(struct Msg msg, int fd);
int main(int argc, char **argv)
{
int s_fd;
int c_fd;
int clen;
int s_read;
char readBuf[128];
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
if(argc != 3){
printf("参数错误!\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);
clen = sizeof(struct sockaddr_in);
while(1){
//4. accept
c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &clen);
if(c_fd == -1){
perror("accept");
exit(-1);
}
printf("与客户端取得连接: %s.\n",inet_ntoa(c_addr.sin_addr));
//5. read & write cycle
if(fork() == 0){
while(1){
memset(msg.data, 0, sizeof(msg.data));
//服务端处于被动状态,等待来自客户端的指令。
s_read = read(c_fd, &msg, sizeof(msg));
if(s_read == 0){
printf("Client Quit.\n");
break;
}else if(s_read > 0){
msg_handler(msg, c_fd);
}
}
}
}
//6. close
close(c_fd);
close(s_fd);
return 0;
}
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;
int ret;
printf("Client's cmd: %s.\n",msg.data);
ret = get_cmd_type(msg.data);
switch(ret){
case LS:
case PWD:
msg.type = 0;
FILE *r = popen(msg.data, "r");
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);
}
}
|客户端代码 client.c
/* client.c */
#define LS 0
#define GET 1
#define PWD 2
#define IFGO 3
#define LCD 4
#define LLS 5
#define CD 6
#define PUT 7
#define QUIT 8
#define DOFILE 9
struct Msg
{
int type;
char data[1024];
char secondBuf[128];
}msg,msgget;
char *getdir(char *cmd);
int get_cmd_type(char *cmd);
int cmd_handler(struct Msg msg, int fd);
void handler_server_message(int c_fd, struct Msg msg);
int main(int argc, char **argv)
{
int c_fd;
int mark;
int ret;
struct sockaddr_in c_addr;
if(argc != 3){
printf("参数错误!\n");
exit(-1);
}
memset(&c_addr, 0, sizeof(struct sockaddr_in));
//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("与服务端建立连接中……\n");
mark = 0;
while(1){
memset(msg.data, 0, sizeof(msg.data));
if(mark == 0){
printf(">>>>");
}
//千万不要使用函数fgets(),继续使用gets(),请忽略编译器给出的警告。
gets(msg.data);
if(sizeof(msg.data) == 0){
if(mark == 1){
printf(">>>>");
}
continue;
}
mark = 1;
ret = cmd_handler(msg, c_fd);
if(ret > IFGO){
printf(">>>>");
fflush(stdout);
continue;
}
if(ret == -1){
printf("指令错误!\n");
printf(">>>>");
fflush(stdout);
continue;
}
handler_server_message(c_fd, msg);
}
return 0;
}
char *getdir(char *cmd)
{
char *p = NULL;
p = strtok(cmd, " ");
p = strtok(NULL, " ");
return p;
}
int get_cmd_type(char *cmd)
{
if(!strcmp("quit", cmd)) return QUIT;
if(!strcmp("ls", cmd)) return LS;
if(!strcmp("lls", cmd)) return LLS;
if(!strcmp("pwd", cmd)) return PWD;
if(strstr(cmd, "lcd")) return LCD;
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;
printf("Cmd_handler: %s\n", msg.data);
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 no exist!\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 s_read;
int newfilefd;
s_read = read(c_fd, &msgget, sizeof(msgget));
if(s_read == 0){
printf("服务端退出!\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));
printf(">>>>");
fflush(stdout);
}else{
printf("__________________________\n");
putchar('\n');
printf("\n%s\n", msgget.data);
printf("__________________________\n");
printf(">>>>");
fflush(stdout);
}
}
|实现效果展示
实现命令 l s ls ls 、 l l s lls lls 与 q u i t quit quit :
实现命令 p w d pwd pwd 、 c d cd cd 与 l c d lcd lcd :
lcd & pwd | cd |
---|---|
实现命令 p u t put put 、 g e t get get:
put | get |
---|---|
思绪
除主函数
m
a
i
n
(
)
main()
main() 以外,服务端 service.c
一共有三个函数模块,客户端 client.c
一共有四个函数模块,两个源文件代码量加一起也就不到 400 行。通过仅仅不到 400 行的代码就对前面学习过的知识点(文件IO、进程、进程间通信等等)进行复盘、深入,这对一位刚刚上手学习 Linux 系统(比如 Ubuntu)的新手而言算得上是最友好的项目之一了。在整个
d
e
m
o
demo
demo 的过程中,我们不过是通过 C 语言编程实现了一个最最基本的 “FTP服务黑箱”,真正实现 FTP 功能的是 Linux *** 作系统,冰山依旧隐藏在海面下方。
待解疑惑
待解决问题:分析函数 fget() 、get() 与 scanf() 的源代码。
对于字符串的输入(stdin)扫描,函数 fgets()
经常是函数 gets()
的替代选项。但两个函数对于扫描字符串命令的输入在 长度检查、缓存边界溢出检查,回车/换行(\n
)(吸收自用 或者 吸收丢弃) 存在细微的差异,关于这一点直接导致笔者调试代码到晚上12点、1点,对,就是这么细微的差异让笔者一度以为是自己的电脑出了问题。
参考博文
- strcmp和!
- Linux小项目FTP云盘实现
- 使用C语言在Liunx环境下作出简易FTP服务器
- Linux应用编程,网络编程练习--------ftp云盘(详细版)
文章更新记录
- “FTP服务功能的代码实现”一节完成。 「2022.6.7 16:59」
- “模仿Liunx系统自身携带FTP服务”一节完成。 「2022.6.10 19:02」
- “实现效果展示”一节完成。 「2022.6.10 19:50」
- “思绪”与“待解疑惑”两节完成。「2022.6.11 8:20」
- 略微修改了个别段落的内容顺序,调整了博文的专栏。「2022.6.13 11:58」
P.S. 1 一篇迟了半年多的博文。「2022.6.11 8:34」
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)