Web服務器文件傳輸程序客戶端程序實現

 

一、 客戶端程序--主函數數組

客戶端主程序的流程圖以下:服務器

 

主程序主要是分析輸入的命令,根據不一樣命令調用不一樣的函數處理或者進行出錯處理,函數代碼以下:網絡

#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;

}
相關文章
相關標籤/搜索