編譯運行:
文檔說明:
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