原创首发于CSDN,转载请注明出处,谢谢!
对于初入 L i n u x Linux Linux 操作系统的新人在学习过 文件IO、进程、进程间通信、线程 等内容对于 Linux 系统具有基本的理解之后,下一步要开始学习 Socket套接字网络编程并以其实现一个最原始的FTP服务。
FTP , File Transfer Protocol(文件传输协议),互联网上专门用来传输文件的协议。对于支持FTP协议的服务器就是FTP服务器,更多内容请自行百度。
利用 C 语言实现 FTP 功能主要在于服务端与客户端的交互命令的设计与 代码实现 ,大致功能命令如下所示(XX代表文件名):
服务端(service):
获取服务端的文件:get xx
展示服务端中的文件:ls xx
进入服务端某个文件夹:cd xx
显示服务端的当前路径:pwd
客户端(client):
查看客户端本地文件:lls xx
进入客户端里的文件夹:lcd xx
客户端上传文件到服务端:put xx
客户端退出:quit
[客户端强制退出时,服务端 read 为零 显示 Client Quit。]
出于文本阅读的简洁考虑,笔者将文本代码中的头文件部分全部删去,感兴趣的读者请自行添加头文件调试。另外,对于代码中九个宏定义内容,读者可保留至自定义的头文件 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 操作系统,冰山依旧隐藏在海面下方。
待解决问题:分析函数 fgets() 、gets() 与 scanf() 的源代码,了解功能函数的实现机制。
对于字符串的输入(stdin)扫描,函数 fgets()
经常是函数 gets()
的替代选项。但两个函数对于扫描字符串命令的输入在 长度检查、缓存边界溢出检查,回车/换行(\n
)(吸收自用 或者 吸收丢弃) 存在细微的差异,关于这一点直接导致笔者调试代码到晚上12点、1点,对,就是这么细微的差异让笔者一度以为是自己的电脑出了问题。
P.S. 1 一篇迟了半年多的博文。「2022.6.11 8:34」