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_INET为IPv4地址族
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
更多推荐
所有评论(0)