FTP上傳下載的斷點續傳實現

                                              FTP上傳下載的斷點續傳實現
                                                                       張國樑  --- CSDN:zglcl008
一:開發背景 
    由於需要對多個服務器發佈大的數據包,所以自己在LINUX用C語言,開發了一個傳送器工具。因爲上傳時需要支持斷點續傳,所以自己參考ftp-rfc959和一些文章開發了這個支持斷點續傳的上傳工具。整個工具分兩部分開發的:一是支持斷點續傳的Ftp工具,包含下載和上傳功能。二是同時對多個服務器發佈不同數據包的傳送器工具。本文只是對斷點續傳的商船和下載作探討。 
 
二:實現
     其實Ftp上傳下載的實現很簡單,首先Ftp服務器端要支持文件的定位,
然後就是通過建立的Socket用Ftp服務器命令和服務器交互.
    有些也可以通過Socket做自己的客戶端和服務器端,但是有些服務器是自己能管理控制的,而且原來自己也寫過一些這樣C/S模式的工具,簡單消息的傳送接收還不錯。
但用來傳輸文件效率很低,比文件傳送協議FTP(File Transfer Protocol)差的太多。  利用Ftp的服務端可以省去服務器端的開發,而且可以向任何開放Ftp服務的服務器上傳送文件,可以不考慮對方安裝的
是什麼的操作系統。
 
三:FTP
 文件傳送協議 FTP 只提供文件傳送的一些基本的服務,它使用 TCP 可靠的運輸服務。FTP 的主要功能是減少或消除在不同操作系統下處理文件的不兼容性。
FTP 使用客戶服務器方式。
一個 FTP 服務器進程可同時爲多個客戶進程提供服務。FTP的服務器進程由兩大部分組成:一個主進程,負責接受新的請求;另外有若干個從屬進程,負責處理單個請求。
A、通常的方式:
控制連接在整個會話期間一直保持打開,FTP 客戶所發出的傳送請求通過控制連接發送給控制進程,但控制連接並不用來傳送文件,實際用於傳輸文件的是“數據連接”。
控制進程在接收到 FTP 客戶發送來的文件傳輸請求後就創建一個“數據傳送進程”和一個“數據連接”,並將數據連接連接到“數據傳送進程”,數據傳送進程實際完成文件的傳送,在傳送完畢後關閉“數據傳送連接”並結束運行
當客戶進程向服務器進程發出建立連接請求時,要尋找連接服務器進程的熟知端口(21),同時還要告訴服務器進程自己的另一個端口號碼,用於建立數據傳送連接。接着,服務器進程用自己傳送數據的熟知端口(20)與客戶進程所提供的端口號碼建立數據傳送連接。

B、 被動模式:
FTP客戶端發出的連接請求,一般通過服務器的21號端口建立控制連接,專門用來傳輸一些字符串命令和響應信息。控制命令通道一定是由客戶端向服務器的連接(默認的端口是21,也可以指定端口,這要看服務器的設置)。
PASV:通過控制通道通過發送PASV 服務器命令到 FTP服務器。請求建立被動模式數據連接通道。 (客戶端的命令 passive)
服務器返回連接的信息(227 Entering Passive Mode (70,0,10,62,120,18) )地址和端口。端口=最後第二位乘256再加上最後一位(120*256+18)。(注意端口設爲0的情況) 如:   *f_port = atoi(port_1) * 256 + atoi(port_2);
服務器端和客戶端身份轉換,原客戶端在本地建立監聽,監聽來自原服務器遠端的連接請求建立數據連接通道。
四、 實現方法:
  A、下載:
1、向服務器發送“REST + 本地文件長度”,告訴服務器,客戶端要斷點下載了。這時服務器還不知道客戶端要下載的文件名;
2、向服務器發送"RETR + 文件名",通知服務器要下載的文件名,這時服務器開始定位文件指針讀文件併發送數據。
3、客戶端定位本地文件指針偏移到文件末尾;
4、兩端的準備工作都做完了以後,客戶端創建socket,以被動或非被動方式建立數據鏈接,循環調用recv接收文件數據並追加到本地文件末尾;

B、上傳:
1、獲取服務器上和本地要上傳文件的同名文件大小;
2、向服務器發送“APPE + 文件名”,通知服務器,從數據通道發送給你的數據要附加到這個文件末尾。
3、定位本地文件指針,文件指針偏移到指定位置,這個位置與FTP服務器上文件大小相同的位置。
4、從文件指針處讀數據併發送。

C、Ftp服務器命令
我們平時使用的命令,大多是客戶端的。服務器端的命令可以參考下面:
命令   描述          
ABOR 中斷數據連接程序      ACCT <account> 系統特權帳號
ALLO <bytes>  爲服務器上的文件存儲器分配字節   APPE <filename> 添加文件到服務器同名文件
CDUP <dir path> 改變服務器上的父目錄   CWD <dir path> 改變服務器上的工作目錄
DELE <filename> 刪除服務器上的指定文件   HELP <command> 返回指定命令信息 LIST <name> 如果是文件名列出文件信息,如果是目錄則列出文件列表  MODE <mode> 傳輸模式(S=流模式,B=塊模式,C=壓縮模式)
MKD <directory> 在服務器上建立指定目錄   NLST <directory> 列出指定目錄內容
NOOP 無動作,除了來自服務器上的承認   PASS <password> 系統登錄密碼
PASV 請求服務器等待數據連接    PORT <address> IP 地址和兩字節的端口 ID PWD 顯示當前工作目錄     QUIT 從 FTP 服務器上退出登錄
REIN 重新初始化登錄狀態連接    REST <offset> 由特定偏移量重啓文件傳遞
RETR <filename> 從服務器上找回(複製)文件  RMD <directory> 在服務器上刪除指定目錄
RNFR <old path> 對舊路徑重命名    RNTO <new path> 對新路徑重命名
SITE <params> 由服務器提供的站點特殊參數  SMNT <pathname> 掛載指定文件結構
STAT <directory> 在當前程序或目錄上返回信息  STOR <filename> 儲存(複製)文件到服務器上
STOU <filename> 儲存文件到服務器名稱上   STRU <type> 數據結構(F=文件,R=記錄,P=頁面)
SYST 返回服務器使用的操作系統    TYPE <data type> 數據類型(A=ASCII,E=EBCDIC,I=binary)
USER <username>> 系統登錄的用戶名
D、服務器返回的部分數字代碼含義
      125 Data connection already open; Transfer starting.
      226 Transfer complete.
      227 Entering Passive Mode (127,0,0,1,4,18).
      230 User xxxxx logged in.
      331 Password required for xxxxx.
      425 Can’t open data connection.
      226 Closing data connection.
      200 return a state of TYPE or MODE commond
      220 connection state
五、關於Socket
     關於Socket編程有很多參考資料,這裏不作詳細說明
Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口
Socket 是一個基本的通信機制Socket把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議.
Socket也具有一個類似於打開文件的函數調用Socket(),該函數返回一個整型的Socket描述符,隨後的連接建立、數據傳輸等操作都是通過該Socket實現的。
常用的Socket類型有兩種:
A、流式Socket(SOCK_STREAM):流式是一種面向連接的Socket,針對於面向連接的TCP服務應用。
B、數據報式Socket(SOCK_DGRAM):數據報式Socket是一種無連接的Socket,對應於無連接的UDP服務應用。
 Socket的程序是一種C/S結構,分客戶端和服務器端。
A、客戶端
– 初始化Socket
– 連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了
– 客戶端發送數據請求,服務器端接收請求並處理請求,然後把迴應數據發送給客戶端
– 客戶端讀取數據,最後關閉連接,一次交互結束。
B、服務器端
- 初始化Socket
- 端口綁定(bind)
- 監聽(listen)端口
- 調用accept阻塞,等待客戶端連接
在這裏我們使用的就是面向連接的流式Socket,只編寫客戶端的程序。

 
六、代碼:
  下面僅提供一個編譯過並在使用中的函數簡單的說明其實現原理,
需要有一點C語言和Linux/UNIX的socket編程基礎即可理解。
 只是建立連接和傳輸部分的一個函數。當文件傳出結束或斷開時,
可以調用檢查函數看是否成功,字節是否正常等,如果不正常結束,
可以再次循環調用它並定爲服務器文件的字節,繼續傳輸。
其他的控制和功能都在其他函數中實現。爲移植方便使用標準C語法符合C89標準。
code:
 /*****************************************************************
 * FileName:     uftt_ver5.c                                      *
 * Company:      algorithmics china lib Co.,Ltd.                  *
 * Author:       G.L.Zhang --- zglcl008                           *
 * Time:         [2006-12-20]                                     *
 * Description:   uninterrupted file transfers tools              *
 *****************************************************************/

int  f_file_trans(const char *ft_addr, int ft_port, const char *ft_usr,const char *ft_pwd, const char *ft_opt, const char *ft_src, char *ft_obj, int ft_flg)
{
    int cmd_sock = -1;
    int dat_sock = -1;
    int stream_sock= -1;
    int dat_port = 0;
    char dat_buffer[1024*5];
    struct sockaddr_in f_server;
    struct sockaddr_in f_datasvr;
    unsigned char *pasv_ip = NULL;   
    unsigned char *pasv_port = NULL;   
    unsigned int len_addr = 0;
    long rc_size = 0;
    /* long svr_file_size = 0; */
    long file_size = 0;
    int rc = 0;
    if (ft_addr == NULL || ft_usr == NULL || ft_pwd == NULL ||
        ft_opt == NULL || ft_src == NULL || ft_obj == NULL ||
        ft_port == 0 || ft_flg < 0) {
        err_quit("--- file transfers parameter error");
    }
    /* get ftp commomd socket */
    if ((cmd_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("cmmond socket");
        err_quit("--- command sock error");
    }
  
    bzero(&f_server, sizeof(f_server));
    f_server.sin_family = AF_INET;
    f_server.sin_port = htons(ft_port);
    f_server.sin_addr.s_addr  = inet_addr(ft_addr);
 
    if (connect(cmd_sock, (struct sockaddr *)&f_server, sizeof(f_server)) < 0) {
        perror("connect");
        err_quit("--- command sock error");
    }
    rc = uftt_cmd(dat_buffer, cmd_sock, NULL);
    if (rc == 220) 
        printf("-1- %d OK/n", rc);
    else
        printf("-1- %d ERR/n", rc);
 
    rc  = uftt_cmd(dat_buffer, cmd_sock,"USER %s",ft_usr);
    if (rc == 331)
        printf("-2- %d OK/n", rc);
    else
        printf("-3- %d ERR/n", rc);
    rc  = uftt_cmd(dat_buffer, cmd_sock,"PASS %s",ft_pwd);
    if (rc == 230)
        printf("-3- %d OK/n", rc);
    else
        printf("-3- %d ERR/n", rc);
    rc = uftt_cmd(dat_buffer, cmd_sock, "TYPE I");
    if (rc == 200)
        printf("-41- %d OK/n", rc);
    else
        printf("-41- %d ERR/n", rc);
    rc = uftt_cmd(dat_buffer, cmd_sock, "MODE S");
    if (rc == 200)
        printf("-42- %d OK/n", rc);
    else
        printf("-42- %d ERR/n", rc);
   
    memset(dat_buffer, '/0', sizeof(dat_buffer));
    rc = uftt_cmd(dat_buffer, cmd_sock, "PASV");
    if (rc == 227)
        printf("-5- %d OK/n", rc);
    else
        printf("-5- %d ERR/n", rc);
#ifdef ZGL_DEBUG
printf("-5- %d %s/n", rc, dat_buffer);
#endif
    /* get passive port */
    dat_port = 0;
    if ((rc = get_svr_port(dat_buffer, (int *)&dat_port)) < 0)
         err_quit("data stream port error");
#ifdef ZGL_DEBUG
printf("--- pasive port=[%d]/n", dat_port);
#endif
    if ((dat_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("data socket");
        err_quit("--- data sock error");
    }
    len_addr = sizeof(f_datasvr);
    bzero(&f_datasvr, sizeof(f_datasvr));
    rc = getsockname(cmd_sock, (struct sockaddr *)&f_datasvr, (unsigned int *)&len_addr);
    f_datasvr.sin_port = htons(dat_port);
    /* 0=all port or appoint port TEST OK ***
    f_datasvr.sin_port = htons(dat_port);
    f_datasvr.sin_port = 0;
    */
    if (bind(dat_sock,(struct sockaddr *)&f_datasvr,len_addr) == -1)
        err_sys("--- data sock bind error");
    if (listen(dat_sock,1) == -1)
        err_sys("--- data sock bind error");
    rc = getsockname(dat_sock, (struct sockaddr *)&f_datasvr, (unsigned int *)&len_addr);
    pasv_ip = (unsigned char *)&f_datasvr.sin_addr;
    pasv_port = (unsigned char *)&f_datasvr.sin_port;
    rc = uftt_cmd(dat_buffer, cmd_sock,"PORT %d,%d,%d,%d,%d,%d",pasv_ip[0],
             pasv_ip[1], pasv_ip[2], pasv_ip[3], pasv_port[0], pasv_port[1]);
    if (rc == 200)
        printf("-6- %d OK/n", rc);
    else
        printf("-6- %d ERR/n", rc);

    /* get RETR put STOR append APPE */
    if (ft_flg == 0) {
        rc = uftt_cmd(dat_buffer, cmd_sock, "STOR %s", ft_obj);
    }
    else if (ft_flg > 0){
        rc = uftt_cmd(dat_buffer, cmd_sock, "APPE %s", ft_obj);
    }
    if (rc == 150)
        printf("-7- %d OK/n", rc);
    else
        printf("-7- %d ERR/n", rc);
    stream_sock = accept(dat_sock, (struct sockaddr *)&f_datasvr, (unsigned int *)&len_addr);
    if (stream_sock < 0)
        err_sys("--- stream socket error");
    rc_size = f_put_file(stream_sock, ft_src, ft_obj, ft_flg, file_size);
    /* do without ***
    rc = uftt_cmd(dat_buffer, dat_sock,"QUIT");
    printf("-8- %d /n", rc);
    */
    close(stream_sock);
    close(dat_sock);
    rc = uftt_cmd(dat_buffer, cmd_sock,"QUIT");
    if (rc == 226)
        printf("-9- %d OK/n", rc);
    else
        printf("-9- %d ERR/n", rc);
    close(cmd_sock);
    return rc_size ;
}
 
 
 
七、參考資料: FTP-RFC959
 

 

發佈了14 篇原創文章 · 獲贊 5 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章