Linux网络编程笔记[1]|套接字Socket实现最简版FTP服务(C语言)

原创首发于CSDN,转载请注明出处,谢谢!


文章目录

    • 模仿 Liunx 系统自身携带FTP服务
      • |最简版 FTP 服务的基本步骤(可配合下一节的代码阅读)
      • |最简版 FTP 服务的基本命令
    • FTP服务功能的代码实现
      • |服务端代码 `service.c`
      • |客户端代码 `client.c`
      • |实现效果展示
    • 思绪
    • 待解疑惑
    • 参考博文
    • 文章更新记录


模仿 Liunx 系统自身携带FTP服务

对于初入 L i n u x Linux Linux 操作系统的新人在学习过 文件IO、进程、进程间通信、线程 等内容对于 Linux 系统具有基本的理解之后,下一步要开始学习 Socket套接字网络编程并以其实现一个最原始的FTP服务。

FTP , File Transfer Protocol(文件传输协议),互联网上专门用来传输文件的协议。对于支持FTP协议的服务器就是FTP服务器,更多内容请自行百度。

|最简版 FTP 服务的基本步骤(可配合下一节的代码阅读)

  1. 服务端运行FTP功能,开辟进程( w h i l e ( ) while() while() & f o r k ( ) fork() fork()),等待客户端的请求;
  2. 客户端运行FTP命令,申请服务端的服务,如 FTP 127.0.0.1 8989
  3. 进程收到客户端的请求后,使用 f o r k ( ) fork() fork() 派生出子进程与客户端进行数据交互,通过套接字 s o c k e t socket socket 管道对文件进行传输;
  4. 客户端输入命令,服务端接收命令,无错的情况下,彼此建立数数据链接(如使用 T C P TCP TCP 端口 8989 进行具体的数据传输,具体代码见下一节内容);
  5. 在完成一次或者多次( w h i l e while while)数据传输之后,客户端输入命令 q u i t quit quit 退出,关闭套接字 s_fdc_fd ,结束整个进程。

|最简版 FTP 服务的基本命令

利用 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

Linux网络编程笔记[1]|套接字Socket实现最简版FTP服务(C语言)_第1张图片

实现命令 p w d pwd pwd c d cd cd l c d lcd lcd

lcd & pwd cd
Linux网络编程笔记[1]|套接字Socket实现最简版FTP服务(C语言)_第2张图片 Linux网络编程笔记[1]|套接字Socket实现最简版FTP服务(C语言)_第3张图片

实现命令 p u t put put g e t get get

put get
Linux网络编程笔记[1]|套接字Socket实现最简版FTP服务(C语言)_第4张图片 Linux网络编程笔记[1]|套接字Socket实现最简版FTP服务(C语言)_第5张图片

思绪

除主函数 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点,对,就是这么细微的差异让笔者一度以为是自己的电脑出了问题。


参考博文

  • 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」
  • 修改标题格式,开放阅读权限。「2023.2.17 15:26」

P.S. 1 一篇迟了半年多的博文。「2022.6.11 8:34」

你可能感兴趣的:(#,Linux网络编程,linux,网络,c语言,socket,FTP)