Linux C小项目 —— 实现文件传输

编译运行:



文档说明:

1、整体程序设计

服务器程序是一个死循环,处理一次连接之后不退出,而客户端程序只处理一个连接就可以了。

2、客户端程序设计

客户端程序的主要任务:

a、分析用户输入的命令;

b、根据命令向服务器端发出请求;

c、等待服务器返回请求的结果。


cd命令和ls命令处理客户机文件系统中的目录,不需要和服务器进行通信,因此不需要建立连接。其它的除了quit命令外,都需要和服务器建立连接,将请求发送给服务器,等待服务器返回结果。quit命令向服务器发出连接结束的请求,之后客户端程序退出,因此不需要等待服务器返回结果。

3、服务器端程序设计

a、分析请求代码;

b、根据请求代码做相应的处理;

c、等待返回结果或应答信息。

有两个主要的环节需要明确:通信协议与服务器模型。

本程序的通信协议分为两种:对于get命令、put命令和!ls命令需要传输文件内容的命令,采用”四次握手“的通信协议;对于!cd命令不需要传输文件内容的命令,采用”两次握手“的通信协议。

“四次握手”:


例如get命令:首先发出GET请求,服务器程序接收到请求后发送确认信息或错误应答码,接收到确认信息后客户端程序发送RDY应答信息,服务器端开始传输文件内容。

“两次握手”:


由于客户端程序是交互式的,因此本程序采用多进程并发服务器模型,如下:

int main(void)
{
	socker();
	bind();
	listen();

	while(1)
	{
		accept();
		if(fork() == 0)	// 子进程处理客户端请求
		{
			while(1)
			{
				close();	// 子进程关闭监听套接字
				read();	write();
			}
			close();	exit();	// 子进程关闭连接套接字并退出
		}
		else
			close();	// 父进程关闭连接套接字
	}
	close();	return 0;	// 父进程关闭监听套接字
}

服务器程序采用并发的方式处理客户端的连接,因此main函数调用fork函数创建一个子进程与客户端的通信,而父进程继续监听其他连接请求。对于交互式的网络程序,这种服务器程序模型不仅仅是可以提高程序执行的效率,更重要的是只有这样才能保证服务器程序不会因为一个连接请求而产生时间阻塞,导致其他连接请求得不到处理。

 

附:

#include <stdlib.h> // exit

#include <unistd.h> // STDOUT_FILENO

 

#include <sys/stat.h> // struct stat 获取文件状态

#include <fcntl.h>

 

#include <sys/socket.h>

#include <netinet/in.h>

 

宏定义调试语句

#define DEBUG_PRINT 1

#ifdef DEBUG_PRINT

#define DEBUG(format, ...) printf(“FILE: “__FILE__”, LINE: %d: “format”\n”, __LINE__, ##__VA_ARGS)

#else

#define DEBUG(format, ...)

#endif

 

Fflsuh(stdout); // 冲洗缓冲区,保证提示符显示

Bzero(&command, sizeof(struct str_command)); // 清零内存空间

Perror(“fail to close”); // 错误提示,系统将自动输出错误号对应的错误信息,此处无需自己添加换行符

System(“ls . > temp.txt”); // system 执行命令

Char *path;  chdir(path); // 改变当前工作目录

 

网络编程

客户端

Struct sockaddr_in serv_addr;

Bzero(serv_addr, sizeof(struct sockaddr_in)); // bzero 清空地址结构

Serv_addr->sin_family = AF_INET; // AF_INETIPv4地址族

Inet_pton(AF_INET, ip, &(serv_addr->sin_addr)); // inet_pton 将点分十进制ip转换为二进制形式,并存储在地址结构中

Serv_addr->sin_port = htons(PORT); // htons 将端口号转换为网络字节序存储在地址结构中

 

*sock_fd = socket(AF_INET, SOCK_STREAM, 0); // socket创建套接字

Connect(*sock_fd, (struct sockaddr *)serv_addr, sizeof(struct sockaddr_in)); // connect 使用该套接字,和填充好的地址结构进行连接

 

服务端

Struct sockaddr_in ser_addr;

Bzero(ser_addr, sizeof(struct sockaddr_in));

Ser_addr->sin_family = AF_INET;

Ser_addr->sin_addr.s_addr = htonl(INADDR_ANY);

Ser_addr->sin_port = htons(PORT);

 

Int listenfd = socket(AF_INET, SOCK_STREAM, 0); // 创建监听套接字

Int sock_opt;

Setsockopt(listenfd, SOL_SOCKET, SO_REUESADDR, &sock_opt, sizeof(int)); // setsockopt 设置套接字选项

Bind(listenfd, (struct sockaddr *)ser_addr, sizeof(struct sockaddr_in)); // bind 绑定客户端地址,具体地址没有限制,为INADDR_ANY

Listen(listenfd, 20); // listen 监听套接字,与客户端的connect函数相互作用

Int connectfd = accept(listenfd, (struct sockaddr *)&cli_addr)// accept

 

读写套接字

Write(sock_fd, buf, strlen(buf)+1);

Len = Read(sock_fd, buf, MAXBUF);

 

字符串操作函数

忽略大小写进行比较 Strcasecmp(command.name, “get”)

复制 strcpy(dest_file, dest);

寻找指定字符在字符串中最后出现的位置 Char *p = rindex(src, ‘/’);

连接字符串 strcat(dest_file, p+1);

是否为其字串,若是则返回首次出现的地址 strstr(buf, “GET”);

 

文件操作

Int fd = open(dest_file, O_WRONLY | O_CREAT | O_TRUNC, 0644); // open 打开

Struct stat stat_buf; fstat(fd, &stat_buf); // struct stat 文件状态

S_ISREG(stat_buf.st_mode) // S_ISREG 是否为普通文件

 

FIEL *fp = fopen(“temp.txt”, “r”); // fopen打开文件

Fgets(buf, MAXBUF, fp); // fgets

Fputs(buf, stdout); // fputs

Unlink(“temp.txt”); // unlink 删除文件

 所有源代码文件汇总:

  1 /*
  2  * FILE: common.h
  3  * DATE: 20180201
  4  * ==============
  5  */
  6 
  7 #include <stdio.h>
  8 #include <stdlib.h>     // exit
  9 #include <unistd.h>     // STDOUT_FILENO
 10 #include <string.h>
 11 
 12 #include <sys/stat.h>   // struct stat
 13 #include <fcntl.h>      // O_WRONLY
 14 
 15 #include <sys/socket.h> // struct sockaddr_in
 16 #include <netinet/in.h>
 17 #define PORT 8000
 18 
 19 #define BUFFSIZE 32
 20 #define MAXBUFF 128
 21 
 22 #define DEBUG_PRINT 1
 23 #ifdef DEBUG_PRINT
 24 #define DEBUG(format, ...) printf("FILE: "__FILE__", LINE: %d: "format"\n", __LINE__, ##__VA_ARGS__)
 25 #else
 26 #define DEBUG(format, ...)
 27 #endif
 28 
 29 // 全局变量声明:命令结构,存储用户输入的命令和参数
 30 struct str_command{
 31         char *name;
 32         char *argv[10];
 33 };
 34 
 35 /* 函数接口声明 */
 36 
 37 // 文件input.c中,处理用户输入
 38 extern int split(struct str_command *command, char *cline);
 39 
 40 // 文件command.c中,命令处理
 41 
 42 extern int do_connect(char *ip, struct sockaddr_in *serv_addr, int *sock_fd);
 43 
 44 extern int do_get(const char *src, const char *dest, int sock_fd);
 45 
 46 extern int do_put(const char *src, const char *dest, int sock_fd);
 47 
 48 extern int do_cd(char *path);
 49 
 50 extern int do_ls(char *path);
 51 
 52 extern int do_ser_ls(char *path, int sockfd);
 53 
 54 extern int do_ser_cd(char *path, int sockfd);
 55 
 56 extern int do_quit(int sock_fd);
 57 
 58 
 59 
 60 
 61 
 62 /*
 63  * FILE: main.c
 64  * DATE: 20180201
 65  * ===============
 66  */
 67 
 68 #include "common.h"
 69 
 70 int main(void)
 71 {
 72         char cline[MAXBUFF];    // 缓冲区,存储用户输入的命令
 73         struct str_command command;     // 命令结构,存储分解后的命令
 74         int sock_fd;
 75         struct sockaddr_in serv_addr;   // 服务器端的地址结构
 76 
 77         printf("myftp$: ");     // 打印提示符
 78         fflush(stdout); // fflush 冲洗,保证提示符显示
 79 
 80         while(fgets(cline, MAXBUFF, stdin) != NULL)     // fgets 得到一行命令
 81         {
 82                 // 自定义split 将命令行拆分为命令和参数
 83                 if(split(&command, cline) < 0)
 84                         exit(-1);
 85 
 86                 // strcasecmp 忽略大小写进行比较
 87                 if(strcasecmp(command.name, "get") == 0)
 88                 {
 89                         if(do_get(command.argv[1], command.argv[2], sock_fd) < 0)
 90                                 exit(-2);
 91                 }
 92                 else if(strcasecmp(command.name, "put") == 0)
 93                 {
 94                         if(do_put(command.argv[1], command.argv[2], sock_fd) < 0)
 95                                 exit(-3);
 96                 }
 97                 else if(strcasecmp(command.name, "cd") == 0)
 98                 {
 99                         if(do_cd(command.argv[1]) < 0)
100                                 exit(-4);
101                 }
102                 else if(strcasecmp(command.name, "ls") == 0)
103                 {
104                         if(do_ls(command.argv[1]) < 0)
105                                 exit(-5);
106                 }
107                 else if(strcasecmp(command.name, "connect") == 0)
108                 {
109                         if(do_connect(command.argv[1], &serv_addr, &sock_fd) < 0)
110                                 exit(-6);
111                 }
112                 else if(strcasecmp(command.name, "!ls") == 0)
113                 {
114                         if(do_ser_ls(command.argv[1], sock_fd))
115                                 exit(-9);
116                 }
117                 else if(strcasecmp(command.name, "!cd") == 0)
118                 {
119                         if(do_ser_cd(command.argv[1], sock_fd))
120                                 exit(-10);
121                 }
122                 else if(strcasecmp(command.name, "quit") == 0)
123                 {
124                         if(do_quit(sock_fd) < 0)
125                                 exit(-8);
126                 }
127                 else
128                 {
129                         printf("ERROR: wrong command\n");
130                         printf("Usage: command argv1 argv2, ...\n");
131                 }
132                 bzero(&command, sizeof(struct str_command));
133                 printf("myftp$: ");     // 再次打印提示符,准备接受新的命令
134                 fflush(stdout);
135         }
136 
137         if(close(sock_fd) < 0)
138         {
139                 perror("fail to close");
140                 exit(-7);
141         }
142         return 0;
143 }
144 
145 
146 
147 
148 
149 
150 /*
151  * FILE: input.c
152  * DATE: 20180201
153  * ==============
154  */
155 
156 #include "common.h"
157 
158 // 宏定义的续行符后 不能有空格或其它内容
159 #define del_blank(p, cline) do{ \
160         while(cline[p]!='\0' && (cline[p]==' ' || cline[p]=='\t')) \
161                 p++; \
162         }while(0)
163 
164 // 宏定义中的变量 不能与函数中的变量 同名
165 #define get_arg(arg, p, cline) do{ \
166         int j = 0; \
167         while(cline[p]!='\0' && cline[p]!=' ' && cline[p]!='\t') \
168                 arg[j++] = cline[p++]; \
169         }while(0)
170 /* 将用户输入的命令字符串分割为命令和参数,
171  * 并存储在自定义的 struct str_command中
172  * command: 存储命令和参数的结构体; cline 用户输入的命令字符串
173  */
174 int split(struct str_command *command, char *cline)
175 {
176         int i=0, p=0;
177 
178         cline[strlen(cline)-1] = '\0';  // 将换行符\n替换为结束符\0
179         del_blank(p, cline);    // 过滤空格,直到遇到第一个参数
180 
181         while(cline[p] != '\0')
182         {
183                 if((command->argv[i]=(char *)malloc(sizeof(char) * BUFFSIZE)) == NULL)
184                 {
185                         perror("fail to malloc");
186                         return -1;
187                 }
188                 get_arg(command->argv[i], p, cline);
189                 /*{
190                         int j = 0;
191                         while(cline[p]!='\0' && cline[p]!=' ' && cline[p]!='\t') 
192                                 command->argv[i][j++] = cline[p++]; 
193                         
194                 }*/
195                 i++;
196                 del_blank(p, cline);
197         }
198 
199         command->argv[i] = NULL;        // 命令参数数组以NULL结尾
200         command->name = command->argv[0];       // 命令名和第一个参数指向同一内存区域
201         return i;
202 }
203 
204 
205 
206 
207 
208 
209 /*
210  * FILE: command.c
211  * DATE: 20180201
212  * ===============
213  */
214 
215 #include "common.h"
216 /* 处理connect命令:connect <ip-address>
217  * 与服务器进行连接
218  * ip: 字符指针,指向服务器地址
219  * serv_addr: 地址结构指针,指向服务器地质结构,在connect函数中填充
220  * sock_fd: 整型指针,指向通信套接字描述符,在connect函数中设置
221  */
222 int do_connect(char *ip, struct sockaddr_in *serv_addr, int *sock_fd)
223 {
224         bzero(serv_addr, sizeof(struct sockaddr_in));   // bzero 清空地址结构
225         serv_addr->sin_family = AF_INET;        // 使用IPv4地址族
226         // inet_pton 将点分十进制的ip地址转换为二进制形式,并存储在地址结构中
227         inet_pton(AF_INET, ip, &(serv_addr->sin_addr));
228         serv_addr->sin_port = htons(PORT);      // htons 将端口号转换为网络字节序存储在地址结构中
229 
230         *sock_fd = socket(AF_INET, SOCK_STREAM, 0);     // 创建套接字
231         if(*sock_fd < 0)
232         {
233                 perror("fail to creat socket");
234                 return -1;
235         }
236         // 使用该套接字,和填充好的地址结构进行连接
237         if(connect(*sock_fd, (struct sockaddr *)serv_addr, sizeof(struct sockaddr_in)) < 0)
238         {
239                 perror("fail to connect");
240                 return -2;
241         }
242         return 0;
243 }
244 
245 /* 处理get命令:get arg1 arg2
246  * 从服务器端取得文件,文件已存在则覆盖
247  * src:源文件的绝对路径,dest:目的目录的绝对路径,sock_fd: 通信用的套接字描述符
248  * client将src_filename传递给server,由server读取文件内容并写入套接字,client读取套接字并写入至文件
249  */
250 int do_get(const char *src, const char *dest, int sock_fd)
251 {
252         char *dest_file;        // 目的路径,dest+filename
253         struct stat stat_buf;   // struct stat 文件状态
254         char *p, buf[MAXBUFF];
255         int fd, len;
256         int res = -1;   // 返回值       
257 
258         if(src==NULL || dest==NULL)     // 检查源文件和目的地址是不是空串
259         {
260                 printf("ERROR: wrong command\n");
261                 return -1;
262         }
263         // 如果源文件路径的最后一个字符是/,则说明源文件不是普通文件,而是目录
264         if(src[strlen(src)-1] == '/')
265         {
266                 printf("source file should be a regular file\n");
267                 return -2;
268         }
269 
270         // malloc 为目标文件路径分配存储空间,由目标目录dest和源文件名组成
271         if((dest_file=(char *)malloc(sizeof(char)*(strlen(dest)+strlen(src)))) == NULL)
272         {
273                 perror("fail to malloc");
274                 return -3;
275         }
276         strcpy(dest_file, dest);
277         if(dest_file[strlen(dest)-1] != '/')
278                 strcat(dest_file, "/");
279         p = rindex(src, '/');   // rindex 取源文件路径中最后一个/的位置指针
280         strcat(dest_file, p+1);
281 
282         if((fd=open(dest_file, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0)
283         {
284                 perror("fail to open dest_file");
285                 goto end2;
286         }
287 
288         if(fstat(fd, &stat_buf) < 0)
289         {
290                 perror("fail to stat dest_file");
291                 goto end1;
292         }
293         // S_ISREG
294         // 如果目标文件已存在,但不是一个普通文件,则无法传输
295         // 否则会造成已存在的目录等其它特殊文件被覆盖   
296         if(!S_ISREG(stat_buf.st_mode))
297         {
298                 printf("dest-file should be a regular file\n");
299                 goto end1;
300         }
301 
302         // 向服务器server发送GET请求
303         sprintf(buf, "GET %s", src);
304         write(sock_fd, buf, strlen(buf)+1);
305         // 服务器的确认信息格式为:“OK 文件名”
306         len = read(sock_fd, buf, MAXBUFF);
307         // 如果收到的信息是ERR,表示出错
308         if(buf[0] == 'E')
309         {
310                 write(STDOUT_FILENO, buf, len);
311                 res = 0;
312                 goto end1;
313         }
314         // len = atoi(&buf[3]);
315         // 告知服务器已准备好RDY,服务器将开始传送文件内容
316         write(sock_fd, "RDY", 3);
317         // 循环读写
318         // read 套接字中服务器传入的内容,write 将读取的内容写至目标文件
319         while((len = read(sock_fd, buf, MAXBUFF)) > 0)
320         {
321                 write(fd, buf, len);
322         }
323         if(len < 0)
324         {
325                 printf("ERROR: read\n");
326                 goto end1;
327         }
328         printf("OK\n");
329         res = 0;
330 end1:
331         close(fd);
332 end2:
333         free(dest_file);        // free 释放malloc分配的内存空间
334         return res;
335 }
336 
337 /*
338  * 处理put命令:put arg1 arg2
339  * 向服务器传送文件,若已存在则覆盖
340  * src:源文件的绝对路径,dest: 目标目录的绝对路径, sock_fd 通信用的套接字描述符
341  * client读取用户指定的src_filename文件内容,并写入至通信套接字;server读取套接字,并写入至文件
342  */
343 int do_put(const char *src, const char *dest, int sock_fd)
344 {
345         char *dest_file;        // 目标文件路径,由dest+filename
346         struct stat stat_buf;   // struct stat 文件状态
347         char *p, buf[MAXBUFF];
348         int fd, len;
349 
350         if(src==NULL || dest==NULL)     // 检查源文件和目的地址是不是空串
351         {
352                 printf("ERROR: wrong command\n");
353                 return -1;
354         }
355         if(src[strlen(src)-1] == '/')   // 源文件名及其绝对路径
356         {
357                 printf("source file should be a regular file.\n");
358                 return -1;
359         }
360         // malloc 为目标文件名及其路径分配内存空间
361         if((dest_file=(char *)malloc(sizeof(char)*(strlen(src)+strlen(dest)))) == NULL)
362         {
363                 perror("fail to malloc");
364                 return -1;
365         }
366         strcpy(dest_file, dest);
367         if(dest_file[strlen(dest_file)-1] != '/')
368                 strcat(dest_file, "/");
369         p = rindex(src, '/');
370         strcat(dest_file, p+1);
371         // open 打开需要传输的源文件
372         if((fd=open(src, O_RDONLY)) < 0)        // open, int fd, write read
373         {
374                 perror("fail to src-file");
375                 goto end1;
376         }
377         if(fstat(fd, &stat_buf) < 0)    // struct stat 源文件状态
378         {
379                 perror("fail to open src-file");
380                 goto end2;
381         }
382         if(!S_ISREG(stat_buf.st_mode))  // 只能是普通文件
383         {
384                 fprintf(stderr, "src-file should be a regular file\n");
385                 goto end2;
386         }
387         sprintf(buf, "PUT %s", dest_file);      // 向服务器发送PUT请求
388         write(sock_fd, buf, strlen(buf)+1);
389         read(sock_fd, buf, BUFFSIZE);   // 接收服务器的确认信息
390         if(buf[0] == 'E')       // 若收到的信息是ERR,表示出错;否则得到RDY应答
391         {
392                 write(STDOUT_FILENO, buf, strlen(buf)+1);
393                 goto end2;
394         }
395         // 循环读取文件内容,并写入至通信套接字传输给服务端
396         while((len=read(fd, buf, MAXBUFF)) > 0)
397                 write(sock_fd, buf, len);
398         if(len<0)       // 读操作出错
399         {
400                 perror("fail to read");
401                 goto end2;
402         }
403         printf("OK\n");
404 end1:
405         close(fd);
406 end2:
407         free(dest_file);        // free 释放malloc分配的内存空间
408         return 0;
409 }
410 
411 /*
412  * 处理ls命令:ls arg1
413  * path: 指定的目录,绝对路径
414  */
415 int do_ls(char *path)
416 {
417         char cmd[64], buf[MAXBUFF];
418         FILE *fp;
419         sprintf(cmd, "ls %s > temp.txt", path); // 拼接命令
420         system(cmd);    // system 执行命令
421         if((fp = fopen("temp.txt", "r")) == NULL)       // fopen, FILE *fp, fgets fputs
422         {
423                 perror("fail to ls");
424                 return -1;
425         }
426         while(fgets(buf, MAXBUFF, fp) != NULL)  // fgets, fputs
427                 fputs(buf, stdout);
428         fclose(fp);
429         //unlink("temp.txt");
430         return 0;
431 }
432 
433 /* 处理!ls命令: !ls arg1
434  * 列出服务器中指定目录的所有文件
435  * path: 指定的目录,绝对路径
436  */
437 int do_ser_ls(char *path, int sockfd)
438 {
439         char cmd[BUFFSIZE], buf[MAXBUFF];
440         int len;
441 
442         sprintf(cmd, "LS %s", path);
443         if(write(sockfd, cmd, strlen(cmd)+1) < 0)       // write 向服务器发送LS请求
444                 return -1;
445         DEBUG("===to server: %s", cmd);
446         if((len=read(sockfd, cmd, BUFFSIZE)) < 0)       // read 读取服务器的应答码
447                 return -2;
448         if(cmd[0] == 'E')       // 若应答码为ERR,表示出错
449         {
450                 write(STDOUT_FILENO, cmd, len);
451                 return 0;
452         }
453         //len = atoi(&buf[3]);
454         DEBUG("===from server: %s", cmd);
455         if(write(sockfd, "RDY", 4) < 0) // 告知服务器已准备好RDY
456                 return -3;
457         // read, write 循环读取服务端传输的内容,并输出到屏幕
458         while((len=read(sockfd, buf, MAXBUFF))>0)
459                 write(STDOUT_FILENO, buf, len);
460         if(len < 0)
461         {
462                 perror("fail to read");
463                 return -4;
464         }
465         printf("!ls OK\n");
466         return 0;
467 }
468 
469 /* 处理cd命令:cd arg1 
470  * path: 指定的目录,绝对路径
471  */
472 int do_cd(char *path)
473 {
474         if(chdir(path) < 0)     // chdir 改变当前工作目录
475         {
476                 perror("fail to change directory");
477                 return -1;
478         }
479         return 0;
480 }
481 
482 /*
483  * 处理!cd命令: !cd arg1
484  * 进入服务器中指定的目录
485  * path: 指定的目录,绝对路径
486  * sockfd: 通信套接字描述符
487  */
488 int do_ser_cd(char *path, int sockfd)
489 {
490         char buf[BUFFSIZE];
491         int len;
492 
493         sprintf(buf, "CD %s", path);
494         if(write(sockfd, buf, strlen(buf)) < 0) // write 向服务器发送CD请求
495                 return -1;
496         if((len=read(sockfd, buf, BUFFSIZE)) < 0)       // read 读取服务器的应答信息
497                 return -2;
498         if(buf[0] == 'E')       // 若应答码为ERR,表示出错
499                 write(STDOUT_FILENO, buf, len);
500         return 0;
501 
502 }
503 
504 /* 处理quit命令: quit
505  * 向服务器发送 关闭连接的请求,然后退出客户端程序
506  * sockfd: 通信用的套接字描述符
507  * 这次通信不需要应答码,因为客户端程序发送命令后已退出,无法处理应答码
508  */
509 int do_quit(int sock_fd)
510 {
511         char buf[4];
512         sprintf(buf, "BYE");
513         // write 向服务器发送关闭连接的请求
514         if(write(sock_fd, buf, strlen(buf)+1) != strlen(buf))
515                 return -1;
516         return 0;
517 }
518 
519 
520 
521 
522 
523 #
524 # FILE: Makefile
525 # DATE: 20180201
526 # ==============
527 
528 OBJECTS = main.o input.o command.o
529 
530 clinet: $(OBJECTS)
531         gcc -o client -g $(OBJECTS)
532 
533 main.o: common.h main.c
534         gcc -c -g main.c
535 input.o: common.h input.c
536         gcc -c -g input.c
537 command.o: common.h command.c
538         gcc -c -g command.c
539 
540 .PHONY: clean
541 clean:
542         rm *.o
543 
547 
548 
549 
550 
551 // ================================================
552 //              server 服务端
553 // ================================================
554 
555 
556 /*
557  * FILE: common.h
558  * DATE: 20180201
559  * ==============
560  */
561 
562 #include <stdio.h>
563 #include <stdlib.h>
564 #include <string.h>
565 #include <fcntl.h>
566 #include <sys/stat.h>
567 #include <errno.h>
568 
569 #include <sys/socket.h>
570 #include <netinet/in.h>
571 #define PORT 8000       // 端口号
572 
573 #define BUFFSIZE 64 
574 #define MAXBUFF 128
575 
576 /* 函数结构声明, command.c文件中定义函数 */
577 int init(struct sockaddr_in *ser_addr, int *lis_fd, int sock_opt);
578 int do_put(int sockfd, char *file);
579 int do_get(int sockfd, char *file);
580 int do_ls(int sockfd, char *path);
581 int do_cd(int sockfd, char *path);
582 
583 
584 
585 
586 /* FILE: main.c
587  * DATE: 20180201
588  * ==============
589  */
590 
591 #include "common.h"
592 
593 int main(void)
594 {
595         struct sockaddr_in ser_addr, cli_addr;  // 服务/客户端地址结构
596         char buf[BUFFSIZE];
597         int listenfd, connfd;
598         int sock_opt, len;
599         pid_t pid;
600 
601         // 自定义init初始化,得到地址结构和监听套接字描述符     
602         if(init(&ser_addr, &listenfd, sock_opt) < 0)
603                 exit(-1);
604         printf("waiting connections ...\n");
605         while(1)        // while死循环,处理客户端请求
606         {
607                 // accept 接收请求
608                 if((connfd=accept(listenfd, (struct sockaddr *)&cli_addr, &len)) < 0)
609                 {
610                         perror("fail to accept");
611                         exit(-2);
612                 }
613                 if((pid=fork()) < 0)    // fork 创建子进程
614                 {
615                         perror("fail to fork");
616                         exit(-3);
617                 }
618                 if(pid == 0)    // 子进程处理连接请求,父进程继续监听
619                 {
620                         close(listenfd);        // 子进程中关闭继承而来的监听套接字
621                         // 本程序的客户端是一个交互式程序,服务器端也是交互的
622                         while(1)
623                         {
624                                 if(read(connfd, buf, BUFFSIZE) < 0)
625                                         exit(-4);
626                                 // strstr(str1, str2) 判断str2是否为str1的字串。
627                                 // 若是,则返回str2在str1中首次出现的地址;否则,返回NULL
628                                 if(strstr(buf, "GET") == buf)
629                                 {
630                                         if(do_put(connfd, &buf[4]) < 0)
631                                                 printf("error occours while putting\n");
632                                 }
633                                 else if(strstr(buf, "PUT") == buf)
634                                 {
635                                         if(do_get(connfd, &buf[4]) < 0)
636                                                 printf("error occours while getting\n");
637                                 }
638                                 else if(strstr(buf, "CD") == buf)
639                                 {
640                                         if(do_cd(connfd, &buf[4]) < 0)
641                                                 printf("error occours while changing directory\n");
642                                 }
643                                 else if(strstr(buf, "LS") == buf)
644                                 {
645                                         if(do_ls(connfd, &buf[3]) < 0)
646                                                 printf("error occours while listing\n");
647                                 }
648                                 else if(strstr(buf, "BYE") == buf)
649                                         break;
650                                 else
651                                 {
652                                         printf("wrong command\n");
653                                         exit(-5);
654                                 }
655                         }
656                         close(connfd);  // 跳出循环后关闭连接套接字描述符,通信结束
657                         exit(0);        // 子进程退出
658                 }
659                 else
660                         close(connfd);  // 父进程关闭连接套接字,继续监听
661         }
662         return 0;
663 }
664 
665 
666 
667 
668 
669 /*
670  * FILE: command.c
671  * DATE: 20180201
672  * ===============
673  */
674 
675 #include "common.h"
676 
677 // 初始化服务器
678 // ser_addr: 服务端地址结构指针; lis_fd: 监听套接字描述符; sock_opt: 套接字选项
679 int init(struct sockaddr_in *ser_addr, int *lis_fd, int sock_opt)
680 {
681         int fd;
682 
683         bzero(ser_addr, sizeof(struct sockaddr_in));    // bzero
684         ser_addr->sin_family = AF_INET; // AF_INET
685         ser_addr->sin_addr.s_addr = htonl(INADDR_ANY);  // htonl(INADDR_ANY)
686         ser_addr->sin_port = htons(PORT);       // htons(PORT)
687 
688         if((fd=socket(AF_INET, SOCK_STREAM, 0)) < 0)    // socket 创建监听套接字
689         {
690                 perror("fail to creat socket");
691                 return -1;
692         }
693         // 设置套接字选项
694         setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &sock_opt, sizeof(int));
695         // bind 绑定客户端地址
696         if(bind(fd, (struct sockaddr *)ser_addr, sizeof(struct sockaddr_in)) < 0)
697         {
698                 perror("fail to bind");
699                 return -2;
700         }
701         //  listen 监听套接字,与客户端的connec函数相互作用
702         if(listen(fd, 20) < 0)
703         {
704                 perror("fail to listen");
705                 return -3;
706         }
707         *lis_fd = fd;
708         return 0;
709 }
710 
711 /* 处理来自客户端的GET命令: GET arg1 arg2
712  * 服务端读取客户端指定的文件,并写入至套接字
713  * sock_fd: 连接套接字描述符
714  * file: 客户端请求的文件及其路径
715  */
716 int do_put(int sockfd, char *file)
717 {
718         struct stat stat_buf;
719         int len, fd;
720         char buf[BUFFSIZE];
721         int res = -1;
722 
723         if((fd=open(file, O_RDONLY)) < 0)       // open 客户端请求的文件
724         {
725                 write(sockfd, "ERROR: fail to open server file\n",
726                         strlen("ERROR: fail to open server file\n"));
727                 return -1;
728         }
729         if(fstat(fd, &stat_buf) < 0)    // struct stat 文件状态
730         {
731                 write(sockfd, "ERROR: fail to stat server file\n",
732                         strlen("ERROR: fail to stat server file\n"));
733                 goto end;
734         }
735         if(!S_ISREG(stat_buf.st_mode)) // 若不是普通文件,则报错
736         {
737                 write(sockfd, "ERROR: not a regular file\n",
738                         strlen("ERROR: not a regular file\n"));
739                 goto end;
740         }
741         sprintf(buf, "OK. FILE SIZE: %d", stat_buf.st_size);
742         write(sockfd, buf, strlen(buf));        // 向客户端发送应答信息:OK 文件大小
743         read(sockfd, buf, MAXBUFF);     // 等待客户端的应答信息,应答码为RDY
744         while((len=read(fd, buf, MAXBUFF)) > 0) // 循环读取文件内容,并写入通信套接字
745                 write(sockfd, buf, len);
746         if(len<0 && errno==EINTR)
747         {
748                 perror("fail to read");
749                 goto end;
750         }
751         printf("OK\n");
752         res = 0;
753 end:
754         close(fd);      // 关闭文件,注意不是关闭套接字
755         return res;
756 }
757 
758 /* 处理客户端的PUT请求: put arg1 arg2
759  * 读取客户端写在通信套接字中的文件内容,并写入至文件
760  * sockfd: 连接套接字的描述符
761  * file: 指定的目标文件名及其路径
762  */
763 int do_get(int sockfd, char *file)
764 {
765         struct stat stat_buf;
766         char buf[MAXBUFF];
767         int fd, len;
768         int res = -1;
769         fprintf(stdout, "===getting file: %s\n", file);
770         // open 打开文件。打开方式是覆盖写,若文件存在则覆盖,但若是一个同名的目录则报错
771         if((fd=open(file, O_RDONLY | O_CREAT | O_TRUNC, 0644)) < 0)
772         {
773                 if(errno == EISDIR)     // 不是普通文件,而是一个目录
774                 {
775                         write(sockfd, "ERROR: server has a dir with the same name\n",
776                                 strlen("ERROR: server has a dir with the same name\n"));
777                         goto end;
778                 }
779                 else
780                 {
781                         write(sockfd, "ERROR: fail to open server file\n",
782                                 strlen("ERROR: fail to open server file\n"));
783                         goto end;
784                 }
785         }
786         if(fstat(fd, &stat_buf) < 0)    // fstat 获取文件状态
787         {
788                 write(sockfd, "ERROR: fail to stat server file\n",
789                         strlen("ERROR: fail to stat server file\n"));
790                 goto end;
791         }
792         if(!S_ISREG(stat_buf.st_mode))  // 如果不是普通文件,则报错
793         {
794                 write(sockfd, "ERROR: not a regular file\n",
795                         strlen("ERROR: not a regular file\n"));
796                 res = 0;
797                 goto end;
798         }
799         // 向客户端发送应答码
800         write(sockfd, "OK\n", 4);
801         while((len=read(sockfd, buf, MAXBUFF)) > 0)
802                 write(fd, buf, len);
803         if(len<0 && errno==EINTR)
804         {
805                 perror("fail to read");
806                 goto end;
807         }
808         printf("OK\n");
809         res = 0;
810 end:
811         close(fd);
812         return res;
813 }
814 
815 /* 处理LS命令: LS arg1
816  * sockfd: 已连接的通信套接字描述符
817  * path: 客户端指定的路径
818  */
819 int do_ls(int sockfd, char *path)
820 {
821         struct stat stat_buf;
822         char cmd[BUFFSIZE], buf[MAXBUFF];
823         int fd, len;
824         int res = -1;
825 
826         sprintf(cmd, "ls %s > temp.txt", path); // 拼接命令
827         fprintf(stdout, "===from client: system(%s)\n", cmd);
828         system(cmd);    // system 执行命令
829 
830         if((fd=open("temp.txt", O_RDONLY)) < 0) // open 打开文件
831         {
832                 write(sockfd, "ERROR: fail to ls server file\n",
833                         strlen("ERROR: fail to ls server file\n"));
834                 return -1;
835         }
836 /*      if(fstat(fd, &stat_buf) < 0)    
837         {
838                 write(sockfd, "ERROR: fail to stat server file\n", 
839                         strlen("ERROR: fail to stat server file\n"));
840                 goto end;
841         }
842         if(!S_ISREG(stat_buf.st_mode))
843         {
844                 write(sockfd, "ERROR: not a regular file\n",
845                         strlen("ERROR: not a regular file\n"));
846                 res = 0;
847                 goto end;
848         }
849         fprintf(stdout, "===to client: OK %d\n", stat_buf.st_size);
850         sprintf(cmd, "OK %d", stat_buf.st_size);
851         write(sockfd, cmd, strlen(cmd)+1); 
852 */
853         write(sockfd, "OK\n", 4);       // 向客户端发送应答信息
854         read(sockfd, cmd, BUFFSIZE);    // 等待客户端的应答信息,应答码为RDY
855 
856         while((len=read(fd, buf, MAXBUFF)) > 0) // 循环读写文件内容,并写入至通信套接字
857                 write(sockfd, buf, len);
858         if(len < 0)
859         {
860                 perror("fail to read");
861                 goto end;
862         }
863         printf("!ls OK\n");
864         res = 0;
865 
866 end:
867         close(fd);
868 //      unlink("temp.txt");     // unlink 删除该临时文件
869         return res;
870 }
871 
872 /* 处理客户端的CD命令: CD arg1 */
873 int do_cd(int sockfd, char *path)
874 {
875         if(chdir(path) < 0)     // chdir 改变当前工作目录,进入指定目录
876         {
877                 perror("fail to change directory\n");
878                 write(sockfd, "ERROR: cannot change server directory\n",
879                         strlen("ERROR: cannot change server directory\n"));
880                 return -1;
881         }
882         write(sockfd, "OK\n", 3);
883         return 0;
884 }
885 
886 
887 
888 
889 
890 
891 # FILE: Makefile
892 # DATE: 20180201
893 # ==============
894 
895 
896 #OBJECTS = common.h main.c command.c
897 
898 #all: server
899 #server: $(OBJECTS)
900 #       gcc -o server $(OBJECTS)
901 #
902 OBJECTS = main.o command.o
903 
904 server: $(OBJECTS)
905         gcc -o server -g $(OBJECTS)
906 
907 main.o: common.h main.c
908         gcc -c -g main.c
909 
910 command.o: common.h command.c
911         gcc -c -g command.c



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章