一、 客戶端程序--主函數數組
客戶端主程序的流程圖以下:服務器
主程序主要是分析輸入的命令,根據不一樣命令調用不一樣的函數處理或者進行出錯處理,函數代碼以下:網絡
#include "common.h" int main(void) { char cline[COMMAND_LINE]; /* 緩衝區,存儲用戶輸入的命令 */ struct command_line command; /* 命令結構,存儲分解後的命令 */ int sock_fd; struct sockaddr_in sin; /* 服務器端的地址結構 */ printf("myftp$: "); /* 打印提示符 */ fflush(stdout); /* 保證提示符顯示 */ while(fgets(cline, MAX_LINE, stdin) != NULL){ /* 獲得一行命令 */ if(split(command, cline) == -1) /* 將命令拆分爲命令名和命令參數的形式*/ exit(1); /* 根據命令名做命令處理,比較命令名和每個合法命令,匹配則處理 */ if(strcasecmp(command.name, "get") == 0){ /* get命令 */ if(do_get(command.argv[1], command.argv[2], sock_fd) == -1) exit(1); /* 命令處理出錯則退出 */ }else if(strcasecmp(command.name, "put") == 0){ /* put命令 */ if(do_put(command.argv[1], command.argv[2], sock_fd) == -1) exit(1); }else if(strcasecmp(command.name, "cd") == 0){ /* cd命令 */ if(do_cd(command.argv[1]) == -1) exit(1); }else if(strcasecmp(command.name, "!cd") == 0){ /* !cd命令 */ if(do_serv_cd(command.argv[1], sock_fd) == -1) exit(1); }else if(strcasecmp(command.name, "ls") == 0){ /* ls命令 */ if(do_ls(command.argv[1]) == -1) exit(1); }else if(strcasecmp(command.name, "!ls") == 0){ /* !ls命令 */ if(do_serv_ls(command.argv[1], sock_fd) == -1) exit(1); }else if(strcasecmp(command.name, "connect") == 0){ /* connect 命令 */ if(do_connect(command.argv[1], &sin, &sock_fd) == -1) exit(1); }else if(strcasecmp(command.name, "bye") == 0){ /* bye命令 */ if(do_bye(sock_fd) == -1) exit(1); break; /* 向服務器端發送退出信息後,則跳出循環,退出程序 */ }else{ /* 錯誤命令,打印程序的用法 */ printf("wrong command\n"); printf("usage : command arg1, arg2, ... argn\n"); } printf("myftp$: "); /* 再次打印提示符,準備接受新的命令 */ fflush(stdout); } if(close(sock_fd) == -1){ /* 執行bye命令後執行到這裏,關閉通訊用套接字 */ perror("fail to close"); exit(1); } return 0; /* 程序正常退出 */ }
二、客戶端程序--命令拆分socket
命令拆分程序能夠根據以前的程序設計來進行編寫,先判斷是什麼命令,再根據命令類型的不一樣調用不一樣的函數,將命令中的參數分離出來做爲。主程序中調用該程序,根據不一樣的命令調用不一樣的函數,而且將參數傳遞給處理函數,流程圖以下:函數
程序代碼以下:spa
#include "common.h" /*下面的宏將cline串中從pos所表示的位置開始,跳過連續的空格和製表符 */ #define del_blank(pos, cline); { \ while(cline[pos] != '\0' && (cline[pos] == ' ' || cline[pos] == '\t'))\ { \ pos++; \ } \ } /* 下面的宏獲得一個命令參數。 * 將cline串中從pos所表示的位置的內容複製到arg緩衝區中,直到遇到空格、製表符或者結束符爲止 */ #define get_arg(arg, pos, cline); { \ int i = 0; \ while(cline[pos] != '\0' && cline[pos] != ' '&& cline[pos] != '/t'){ \ arg[i++] = com[pos++]; \ } \ } /* 將一個命令字符串分割爲命令參數並存儲在command_struct結構中 * 成功則返回拆分後的命令參數的個數,失敗返回-1 * command : 存儲命令和命令參數的結構體 * cline : 命令字符串 */ int split(struct command_line * command, char cline[ ]) { int i; int pos = 0; clien[strlen(cline) - 1] = '\0'; /* 獲得命令字符串的長度,將最後一個‘\n’ 替換爲‘\0’ */ del_blank(pos, cline); /* 過濾空格,直到遇到第一個參數 */ i = 0; while(cline[pos] != '\0'){ /* 處理整個命令字符串 */ /* 爲存儲命令參數的數組分配空間 */ if((command->argv[i] = (char *)malloc(MAX_LENGTH)) == NULL){ perror("fail to malloc"); return -1; } /* 獲得一個參數,將兩個空格之間的內容複製到存儲參數的數組 */ get_arg(command->argv[i], pos, cline); i++; /* 下一個參數 */ del_blank(pos, cline); /* 過濾掉空格 */ } command->argv[i] = NULL; /* 命令參數數組以NULL結尾 */ comand->name = command->.argv[0]; /* 命令名和第一個命令參數實際上指向同一塊內存區域 */ return i; }
三、客戶端程序—命令模塊處理設計
命令處理模塊是這個程序的核心模塊,客戶端的命令處理程序有7個。指針
(1)do_connect()函數code
do_connect()函數完成了建立套接字而且鏈接的工做,這個函數返回說明客戶端已經和服務器成功鏈接。do_connect()函數處理connect命令,成功返回0,失敗返回-1。connect命令的形式爲:connect arg1,參數 arg1是服務器的IP地址。orm
參數說明:
ip : 字符指針,指向服務器的地址,是一個十進制點分字符串;
sin : 地質結構指針,指向服務器地址結構,在connect函數中填充;
sock_fd : 整型指針,指向通訊用套接字描述符,該套接字在connct函數中設置,將描述符傳回。
int do_connect(char *ip,struct sockaddr_in *sin, int *sock_fd) { int sfd; /* 臨時的套接字描述符 */ bzero(&sin, sizeof(struct sockaddr_in)); /* 將地址結構清空 */ sin.sin_family = AF_INET; /* 使用IPv4地址族 */ /* 將點分十進制形式的IP地址轉換成爲二進制形式的地址,而且存儲在地址結構中 */ if(inet_pton(AF_INET, ip, &sin.sin_addr) == -1){ perror("wrong format of ip address"); /* 若是地址格式爲非法則出錯 */ return -1; } sin.sin_port = htons(PORT); /* 將端口號轉換成爲網絡字節序存儲在地址結構中 */ if(sfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){ /* 建立套接字 */ perror("fail to creat socket"); return -1; } /* 使用該套接字,和填充好的地址結構進行鏈接 */ if(connect(sfd, (struct sockaddr *)sin, sizeof(struct sockaddr_in)) == -1){ perror("fail to connect"); return -1; } *sock_fd = sfd; /* 將sfd保存的套接字的描述符賦值給sock_fd所指向區域返回 */ return 0; }
(2)do_get函數處理get命令,成功返回0,失敗返回-1。get命令的形式爲:get arg1 arg2。該命令從服務器端取得文件,arg1表示源文件,arg2表示目的路徑,若是該文件已存在則覆蓋。
參數說明:
src : 源文件,是一個絕對路徑
dst : 目的目錄,是一個絕對的路徑
sock_fd : 通訊用的套接字描述符
int do_get(const char *src, const char *dst, int sock_fd) { char *dst_file; /* 目的路徑 */ char *p; struct stat statbuf;/* 文件狀態緩衝區 */ int n, fd; char buf[MAX_LINE];/* 命令緩衝區,存儲發送給服務器端的命令 */ int len; int res = -1; /* 返回值默認的值爲-1 */ if(src == NULL || dst == NULL){/* 檢查源文件和目的地址是否是空串 */ printf(wrong command\n); /* 是空串則報錯 */ return -1; } /* 若是源文件路徑的最後一個字符是‘/’,則說明源文件不是一個普通文件,而是一個目錄 */ if(src[strlen(src)-1]=='/'){ printf("source file should be a regular file\n"); return -1; } /* 爲最終的目的文件路徑分配內存空間 * 文件路徑由目標目錄和源文件的文件名組成 * 因此該緩衝區的大小最大是兩個文件路徑的長 */ if( (dst_file = (char *)malloc(strlen(dst) + strlen(src))) == NULL){ perror("fail to malloc"); return -1; /* 若是內存分配不成功則返回-1 */ } strcpy(dst_file, dst); /* 將目標目錄複製到dst_file緩衝區中 */ /* 若是目標目錄的結尾不是‘/’,則添加‘/’,例如/home/admin—>/home/admin/ */ if(dst_file[strlen(dst_file) - 1] != '/') strcat(dst_file, "/"); p = rindex(src, '/'); /* 取源文件路徑中最後一個‘/’,其後面是文件名 */ strcat(dst_file, p + 1); /* 跳過‘/’,獲得源文件文件名 */ /* 若是目標文件不存在則建立該文件,使用權限字「0644」 * 該權限表示全部者擁有讀寫權限而組用戶和其餘用戶只有讀權限 * 若是該文件存在,則將文件截短爲0打開,覆蓋原文件 */ if((fd = open(dst_file, O_WRONLY | O_CREAT | O_TRUNC, 0644)) == -1){ perror("fail to open dst-file"); goto end2; /* 打開文件失敗,退出時只須要釋放dst_file的內存,而不須要關閉 文件 */ } if(fstat(fd, &statbuf) == -1){ /* 取目標文件的文件狀態 */ perror("fail to stat dst-file"); goto end1; } /* 若是目標文件已經存在,且不是一個普通文件,則沒法傳輸 * 由於這樣會形成將已經存在的目錄等其餘特殊文件覆蓋 */ if(!S_ISREG(statbuf.st_mode)){ printf("dst-file should be a regular file"); goto end1; } sprintf(buf, "GET %s", src); /* 準備向服務器端發送GET命令 */ if(my_write(sock_fd, buf, strlen(buf)+1) == -1) /* 發出命令 */ goto end1; /* 出錯則退出 */ if( (n = my_read(sock_fd, buf, MAX_LINE)) <= 0){ /* 接收服務器端發回的確 認信息 */ goto end1; } if(buf[0] == 'E'){ /* 若是收到的信息是ERR,表示出錯 */ write(STDOUT_FILENO, buf, n); /* 向屏幕輸出服務器發來的出錯信息 */ res = 0; goto end1; } /* 若是對沒有出錯,服務器反饋的信息格式爲「OK 請求文件的文件大小」 * 跳過字符串「OK」和一個空格,從第三個字符開始,取得文件的長度 */ len = atoi(&buf[3]); /* 向服務器發出準備好的信息,服務器接收到此信息後開始傳送文件的內容 */ if(my_write(sock_fd, "RDY", 3) == -1) goto end1; while(1){ /* 循環讀,直到讀完全部的文件內容 */ n = my_read(sock_fd, buf, MAX_LINE); /* 讀服務器傳送來的內容 */ if(n > 0){ /* 讀到的字節數大於0,說明正常 */ write(fd, buf, n); /* 將讀到的內容寫到打開的目標文件中去 */ len -= n; /* 文件總長度減小 */ }else if(len == 0){ /* 讀到的字節數等於0,說明通訊已經結束 */ /* 若是請求的文件已經讀完,則正常結束這次命令的執行 */ printf("OK\n"); break; }else /* 讀到的字節數小於0,出錯 */ goto end1; } res = 0; /* 運行到此,則函數正常返回,返回值爲0 */ end1: free(dst_file); /* 釋放動態分配的內存空間 */ end2: close(fd); /* 關閉文件 */ return res; }