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



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