FTP協議編程看這一篇文章就夠了

1、FTP 概述

     文件傳輸協議(FTP)作爲網絡共享文件的傳輸協議,在網絡設備中具有廣泛的應用,FTP協議用於實現網絡中不同設備之間的文件傳輸與共享,這個是一個網絡協議與具體的操作系統無關,只要你所使用的操作系統支持此協議就可以進行文件傳輸。

      FTP協議中的設備分爲客戶端和服務器2種角色,在傳輸文件時,FTP 客戶端程序先與服務器建立連接,然後向服務器發送命令。服務器收到命令後給予響應,執行相應命令的操作。本文中所模擬的FTP通信協議的過程,客戶端使用windows電腦,在電腦上打開一個tcp調度工具,服務器是阿里雲的服務器,操作系統是Linux,提前在服務器上安裝vsftp,並且做好相關設置。具體的設置可以參考這個文章。當然也可以在本地網絡中找2臺電腦,一臺電腦做爲FTP服務器,運行FileZilla Server軟件即可以實現,另外一臺電腦做爲客戶端。

2、FTP 協議

      HTTP協議是基於TCP協議之上的簡單的協議,FTP 協議相比它要複雜一點,但是當你看完這篇文件後,會感覺FTP協議其實也還是很簡單。HTTP協議通過一個SOCKET連接傳輸一次會話數據,而FTP協議中將控制命令與數據分開傳送的方法提高了效率,FTP協議要創建2個SOCKET TCP連接,佔用2個TCP 端口,一個連接用於傳輸控制命令,另外一個連接用於傳輸數據。

      FTP 使用 2 個端口,一個數據端口和一個命令端口(也叫做控制端口)。這兩個端口一般是21 (命令端口)和 20 (數據端口)。控制 Socket 用來傳送命令,數據 Socket 是用於傳送數據。命令端口中客戶端每一個 FTP 命令發送之後,FTP 服務器都會返回一個字符串,其中包括一個響應代碼和一些說明信息。其中的返回碼主要是用於判斷命令是否被成功執行了。

2.1、命令端口

     客戶端創建一個 Socket 連接 FTP 服務器的21端口,此控制連接實現 FTP 命令的發送和接收返回的響應信息。比如:“登錄”、“改變目錄”、“刪除文件”,“上傳文件”,“下載文件”這些操作。

2.2、數據端口

      對於有數據傳輸的操作,客戶端創建一個SOCKET連接, 主要傳輸顯示目錄列表,上傳、下載文件

     在數據傳輸時,有兩種模式:被動模式、主動模式。

     使用被動模式,通常FTP服務器端會返回給客戶端一個端口號(這個端口由FTP服務器自動分配出來)。客戶端需要另開一個 Socket 來連接FTP服務器這個端口,在這個TCP連接會話中FTP服務器做爲TCP服務器,FTP客戶端做爲TCP客戶端,然後根據FTP客戶端操作來發送命令,數據會通過這個新開的一個端口傳輸。

     使用主動模式,通常FTP客戶端會發送一個端口號給FTP服務器,並在這個端口監聽。這個TCP連接會話中FTP服務器做爲TCP客戶端,FTP客戶端做爲TCP服務器。FTP服務器需要連接到FTP客戶端開啓的這個數據端口,並進行數據的傳輸。

 

2.3、主動模式 (PORT)

       主動模式下,FTP客戶端隨機打開一個大於 1024 的端口向服務器的命令端口21,創建控制連接。同時客戶端開放N +1 端口監聽,並向服務器發出 “port N+1” 命令,由服務器從它自己的數據端口 (20) 主動連接到客戶端指定的數據端口 (N+1)。

       FTP 的客戶端只是告訴服務器自己的端口號,等待服務器來連接客戶端這個的端口。一般這種情況下服務器要從公網向客戶端所在的內網發過連接,有些客戶端的內網爲了保證網絡安全,內網防水牆是阻止外網訪問內網的設備

2.4、被動模式 (PASV)

       爲了解決服務器發起內網客戶端會被攔截的問題,FTP協議提供另外一種連接方式:即被動方式。命令連接和數據連接都由客戶端發起。

      被動模式下,FTP客戶端連接服務器的命令端口21,創建控制連接,然後服務器會分配一個端口(端口號大於1024),並且返回給客戶端已經分配的端口號和連接的IP地址。等待客戶端來連接這個端口,創建數據連接,文件傳輸和顯示目錄列表這些信號都在這個數據連接中傳輸。

 

 

2.5、 FTP 命令

       FTP 每個命令都有 3 到 4 個字母組成,命令後面跟參數,用空格分開。每個命令最後都以 "\r\n"結尾,FTP命令在控制連接中傳輸。上傳或下載一個文件時,FTP客戶端要首先登陸FTP服務器(輸入用戶名和密碼),切換到要讀定的服務器目錄,上傳或下載文件,最後退出登陸。這個過程中用的命令有USER, PASS, PASV, PORT,CWD,RETR,QUIT等命令。

 

要下載或上傳一個文件,首先要登入 FTP 服務器,然後發送命令,最後退出。這個過程中,主要用到的命令有 USER、PASS、SIZE、REST、CWD、RETR、PASV、PORT、QUIT。

USER: 指定用戶名。控制連接發現的第一條的命令,向服務器輸入登陸的用戶句。例如 “USER zsm\r\n”: 登陸用戶名爲zsm。輸入的用戶名一定是在FTP服務器提前註冊好的用戶名,否則FTP服務器對輸入的用戶名返回錯誤。

PASS: 指定用戶密碼。該命令緊跟 USER 命令後。“PASS 1982hello\r\n”:密碼爲 1982hello。

SIZE: 從服務器上返回指定文件的大小。“SIZE file.txt\r\n”:如果 file.txt 文件存在,則返回該文件的大小。

CWD: 改變工作目錄。如:“CWD /HDRC-VC0/\r\n”,讓服務器從切換到HDRC-VC0這個文件夾下面。

PASV: 讓服務器在數據端口監聽,進入被動模式。如:“PASV\r\n”。此時服務器會給客戶端返回一個IP地址端口號,例如服務器會返回227 Entering Passive Mode (172,17,92,131,31,139),227是返回的應答碼,後面字符串表示服務器進入了被動模式,括號中的6個數字,前4個數據是服務器的IP地址,後2個數據表示端口號,端口號的計算方法是第一個數字乘以256加上第二個數據,即31*256+139=8075,端口號爲8075,客戶端需要連接8075端口來建立數據連接。

PORT: 告訴 FTP 服務器客戶端監聽的端口號,讓 FTP 服務器進入主動模式,服務器主動連接客戶端端口來建立數據連接。如:“PORT h1,h2,h3,h4,p1,p2”, 服務器獲得到客戶端開放的端口是p1*256+p2,服務器會連接客戶端的這個端口來創建數據連接。

RETR: 下載文件。“RETR file.txt \r\n”:下載文件 file.txt。

STOR: 上傳文件。“STOR file.txt\r\n”:上傳文件 file.txt。

REST: 該命令是文件內數據偏移指令,相當於文件操作的fseek函數,移動文件讀寫指針。此命令後應該跟其它要求文件傳輸的 FTP 命令。“REST 100\r\n”:重新指定文件傳送的偏移量爲 100 字節。

QUIT: 關閉與服務器的連接,退出登陸。

 

2.6、FTP 響應碼

    客戶端發送 FTP 命令後,服務器返回響應碼。

   響應碼用三位數字編碼表示:

    第一個數字給出了命令狀態的一般性指示,比如響應成功、失敗或不完整。含義如下

             1 表示服務器正確接收信息,還未處理。

             2 表示服務器已經正確處理信息。

             3 表示服務器正確接收信息,正在處理。

             4 表示信息暫時錯誤。

            5 表示信息永久錯誤。

    第二個數字是響應類型的分類,如 2 代表跟連接有關的響應,3 代表用戶認證。

 

 

 

 

 

 

 

二個數字的含義如下:

 

0 表示語法。

 

1 表示系統狀態和信息。

2 表示連接狀態。

3 表示與用戶認證有關的信息。

4 表示未定義。

5 表示與文件系統有關的信息。

2.7、socket 編程

Socket 客戶端編程主要步驟如下:

  1. socket() 創建一個 Socket
  2. connect() 與服務器連接
  3. write() 和 read() 進行會話
  4. close() 關閉 Socket連接

Socket 服務器端編程主要步驟如下:

  1. socket() 創建一個 Socket
  2. bind()
  3. listen() 監聽
  4. accept() 接收連接的請求
  5. write() 和 read() 進行會話
  6. close() 關閉 Socket連接

2.8、使用TCP工具來模擬FTP通信數據

       下面我們使用TCP工具向服務器創建連接來模擬與服務器的通信數據,深入學習FTP數據在網絡上是如何編碼和傳輸的。

第一步:打開TCP&UDP測試軟件,這個軟件你可以自行去網上下載,創建一個TCP控制連接,輸入IP地址和端口號(21),創建連接

    

      服務器會返回一條歡迎語,"220 (vsFTPd 3.0.2)\r\n",其中220是應答碼,表示服務器正確的接受了連接,軟件窗口中無法顯示出\r\n所以看不到,vsFTPd 3.0.2是歡迎語,不同的FTP服務器返回的不一樣。如下圖

         第二步,發送登陸用戶名稱密碼,先發送用戶名,在上面軟件的發送窗口中輸入"USER ftpadmin\r\n"由於這個軟件窗口中無法輸入\r\n,所以採用這個字符串對應的16進制數據發送,即在上面的輸入中輸入55 53 45 52 20 66 74 70 61 64 6D 69 6E 0D 0A來表示這個字符串,以下的所有操作最後都是輸入的字符串對應的16進制數據。服務器返回:“331 Please specify the password.\r\n”

         再發送密碼,“PASS 1982long\r\n”,對應的16進制數據:50 41 53 53 20 31 39 38 32 6C 6F 6E 67 0D 0A,服務器返回:“230 Login successful\r\n”

        第三步、切換工作目錄到HDRC-VC0這個目錄,客戶端發送“CWD /HDRC-VC0\r\n"  對應的16進制數據43 57 44 20 2F 48 44 52 43 2D 56 43 30 0D 0A
服務器返回:“250 Directory successfully changed\r\n”

       第四步、設置文件傳輸方式,文件傳輸方式有bin,ascii字符的方式,對於傳輸bin,圖片等數據請使用bin文件的方式,ascii字符的傳輸方式會因爲不同的操作系統對傳輸的內容進行轉碼。客戶端發送”TYPE I\r\n“ 對應的16進制數據54 59 50 45 20 49 0D 0A   服務器返回:”200 Switching to Binary mode.\r\n“

        第五步、讓服務器進行被動模式,客戶端發送"PASV\r\n"  對應的16進制數據  50 41 53 56 0D 0A,服務器返回
”227 Entering Passive Mode (172,17,92,131,31,139)\r\n“.   即服務器開放端口31*256+139=8075端口傳輸文件
此時要讓客戶端新建一個連接到服務器的8075端口,用於接收數據。

         第六步、下載文件,客戶端發送:”RETR HDRC-VC0_V12_20190717.bin\r\n“  對應的16進制數據52 45 54 52 20 48 44 52 43 2d 56 43 30 5f 56 31 32 5f 32 30 31 39 30 37 31 37 2e 62 69 6e 0d 0a服務器返回:”
150 Opening BINARY mode data connection for HDRC-VC0_V12_20190717.bin (90824 bytes).\r\n“,同時服務器開始使用數據連接向客戶端發送HDRC-VC0_V12_20190717.bin文件的內容數據。發送完成後,服務器在控制連接中返回一條應答”226 Transfrer complete.\r\n“表示傳輸完成。

       第七步、退出登陸,關閉連接。客戶端發送”QUIT\r\n“,服務器返回:”221 Logout\r\n“

        上傳文件與下載文件一樣,不再舉例介紹。

2.9  編程實現一個文件下載的功能

       本程序運行在RT thread操作系統之上,使用標準TCP socket接口編程,你也可以把此種移植到其他系統上面來使用。

        下面是文件ftp.c的源代碼。

#include <sys/socket.h>
#include <sys/select.h>
#include <netdb.h>

#include "main.h"
#include "ftp.h"
#include "toolkit\toolkit.h"

#ifdef INCLUDE_FTP

#define DBG_ENABLE
#define DBG_SECTION_NAME "FTP"
#define DBG_LEVEL        DBG_INFO
#include <rtdbg.h>

/*FTP通信管理數據結構*/
FTP_MANAGER   g_ftp_manager;


/*********************************************************************************************************
** Function name:      ftp_wait_server_ack()
** Descriptions:        FTP客戶端等待服務器應答
** input parameters:   無
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
static rt_err_t ftp_wait_server_ack(int socket,  rt_uint32_t *ack_code, rt_uint32_t wait_time)
{
    fd_set readset;
    struct timeval timeout;
    char     ftp_recv_buf[50] = {0};
    int            recv_len = 0;
    int            code= 0;

    timeout.tv_sec = wait_time;
    timeout.tv_usec = 0;

    FD_ZERO(&readset);
    FD_SET(socket, &readset);

    if (select(socket + 1, &readset, RT_NULL, RT_NULL, &timeout) <= 0) {
        LOG_E("select the socket timeout!");
        return -RT_ETIMEOUT;
    }

    // Wait and receive the packet back from the server. If n == -1, it failed.
    recv_len = recv(socket, (char*)ftp_recv_buf, sizeof(ftp_recv_buf), 0);

    if (recv_len < 0) {
        LOG_E("reading from socket error!");
        return -RT_ERROR;
    }

    if(1 == sscanf(ftp_recv_buf, "%d", &code))
    {
        *ack_code = code;
        return RT_EOK;
    }
    else
    {
        return -RT_ERROR;
    }


}

/*********************************************************************************************************
** Function name:      ftp_send_cmd_and_server_ack()
** Descriptions:        FTP客戶端發送命令等待服務器應答
** input parameters:   無
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
static rt_err_t ftp_send_cmd_and_server_ack(int socket, char *cmd, rt_uint32_t cmd_len,rt_uint32_t *ack_code, rt_uint32_t wait_time)
{
    fd_set readset;
    struct timeval timeout;
    char          ftp_recv_buf[100] = {0};
    int            len = 0;
    int            code= 0;

    if((RT_NULL == cmd) || (RT_NULL == ack_code))
    {
        return -RT_ERROR;
    }
    
    timeout.tv_sec = wait_time;
    timeout.tv_usec = 0;

    FD_ZERO(&readset);
    FD_SET(socket, &readset);

    /*發送指令*/
    len = send(socket, (char*)cmd, cmd_len, 0);

    if (len < 0) {
        LOG_E("send cmd error!");
        return -RT_ERROR;
    }
    
    /*等待應答*/
    if (select(socket + 1, &readset, RT_NULL, RT_NULL, &timeout) <= 0) {
        LOG_E("select the socket timeout!");
        return -RT_ETIMEOUT;
    }

    // Wait and receive the packet back from the server. If n == -1, it failed.
    len = recv(socket, (char*)ftp_recv_buf, sizeof(ftp_recv_buf), 0);

    if (len < 0) {
        LOG_E("reading from socket error!");
        return -RT_ERROR;
    }

    if(1 == sscanf(ftp_recv_buf, "%d", &code))
    {
        *ack_code = code;
        return RT_EOK;
    }
    else
    {
        LOG_I("Rcv data:%s", ftp_recv_buf);
        return -RT_ERROR;
    }


}



/*********************************************************************************************************
** Function name:      ftp_pasv_mode()
** Descriptions:        FTP客戶端發送命令給服務器進入pasv模式
** input parameters:   無
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
static rt_err_t ftp_filesize_get(int socket, char *cmd, rt_uint32_t cmd_len, rt_uint32_t *ack_code,  rt_uint32_t *file_size, rt_uint32_t wait_time)
{
    fd_set readset;
    struct timeval timeout;
    char           ftp_recv_buf[100] = {0};
    int            len = 0;
    int            code= 0;
    int            a= 0;

    if((RT_NULL == ack_code) || (RT_NULL == file_size) ||
       (RT_NULL ==  cmd))
    {
        return -RT_ERROR;
    }

    timeout.tv_sec = wait_time;
    timeout.tv_usec = 0;

    FD_ZERO(&readset);
    FD_SET(socket, &readset);

    /*發送指令*/
    len = send(socket, cmd, cmd_len, 0);

    if (len < 0) {
        LOG_E("send cmd error!");
        return -RT_ERROR;
    }
    
    /*等待應答*/
    if (select(socket + 1, &readset, RT_NULL, RT_NULL, &timeout) <= 0) {
        LOG_E("select the socket timeout!");
        return -RT_ETIMEOUT;
    }

    // Wait and receive the packet back from the server. If n == -1, it failed.
    len = recv(socket, (char*)ftp_recv_buf, sizeof(ftp_recv_buf), 0);

    if (len < 0) {
        LOG_E("reading from socket error!");
        return -RT_ERROR;
    }
    
    /*獲取文件大小和應答碼*/
    if(2 == sscanf(ftp_recv_buf, "%d %d", &code, &a))
    {
        *ack_code = code;
        *file_size = a;
        return RT_EOK;
    }
    else
    {
        LOG_E("Rcv data:%s", ftp_recv_buf);
        return -RT_ERROR;
    }


}



/*********************************************************************************************************
** Function name:      ftp_pasv_mode()
** Descriptions:        FTP客戶端發送命令給服務器進入pasv模式
** input parameters:   無
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
static rt_err_t ftp_pasv_mode(int socket,rt_uint32_t *ack_code,  rt_uint32_t *server_port, rt_uint32_t wait_time)
{
    fd_set readset;
    struct timeval timeout;
    char           ftp_recv_buf[80] = {0};
    int            len = 0;
    int            code= 0;
    int            a,b,c,d,e,f = 0;

    if((RT_NULL == ack_code) || (RT_NULL == server_port))
    {
        return -RT_ERROR;
    }

    timeout.tv_sec = wait_time;
    timeout.tv_usec = 0;

    FD_ZERO(&readset);
    FD_SET(socket, &readset);

    /*發送指令*/
    len = send(socket, "PASV\r\n", strlen("PASV\r\n"), 0);

    if (len < 0) {
        LOG_E("send cmd error!");
        return -RT_ERROR;
    }
    
    /*等待應答*/
    if (select(socket + 1, &readset, RT_NULL, RT_NULL, &timeout) <= 0) {
        LOG_E("select the socket timeout!");
        return -RT_ETIMEOUT;
    }

    // Wait and receive the packet back from the server. If n == -1, it failed.
    len = recv(socket, (char*)ftp_recv_buf, sizeof(ftp_recv_buf), 0);

    if (len < 0) {
        LOG_E("reading from socket error!");
        return -RT_ERROR;
    }
    
    /*跳過非數字字符來取出IP和端口號*/
    if(7 == sscanf(ftp_recv_buf, "%d%*[^(](%d,%d,%d,%d,%d,%d", &code,&a,&b,&c,&d,&e,&f))
    {
        *ack_code = code;
        *server_port= e *256+f;
        return RT_EOK;
    }
    else
    {
        LOG_I("Rcv data:%s", ftp_recv_buf);
        return -RT_ERROR;
    }


}

/*********************************************************************************************************
** Function name:      ftp_data_thread()
** Descriptions:        ftp數據通信線程
** input parameters:   無
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
static void ftp_data_thread(void *parameter)
{
    struct timeval timeout;
    fd_set readset;
    char     ftp_data_buf[1400] = {0};
    int      recv_len = 0;
    int      socket = 0;
    rt_uint32_t recv_sum = 0;

    socket = g_ftp_manager.ftp_data_socket;
    
    FD_ZERO(&readset);

    timeout.tv_sec = 20;
    timeout.tv_usec = 0;

    
    for(;;)
    {
        FD_SET(socket, &readset);
        if (select(socket + 1, &readset, RT_NULL, RT_NULL, &timeout) <= 0) {
            LOG_E("select data socket timeout!");
            break;
        }

        // Wait and receive the packet back from the server. If n == -1, it failed.
        recv_len = recv(socket, (char*)ftp_data_buf, sizeof(ftp_data_buf), 0);

        if (recv_len <= 0) 
        {
            LOG_E("reading data socket error!");
            break;
        }
        else
        {   
            /*這裏就是接收到的文件內容數據,只是打印出接收的文件長度,並沒有存儲到本地*/
            recv_sum = recv_sum + recv_len;
            LOG_I("Rcv sum = %d, len = %d", recv_sum, recv_len);

            if(recv_sum == g_ftp_manager.file_size)
            {
                LOG_I("Rcv complete!", recv_sum, recv_len);
                break;
            }
        }

    
    }

    closesocket(g_ftp_manager.ftp_data_socket);
    g_ftp_manager.ftp_data_socket = -1;


}

/*********************************************************************************************************
** Function name:      ftp_control_thread()
** Descriptions:        FTP控制線程
** input parameters:   無
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
static void ftp_control_thread(void *parameter)
{
    rt_thread_t  ftp_thread = NULL;
    struct hostent *server = RT_NULL;
    struct sockaddr_in serv_addr;
    rt_uint32_t  ack_code = 0;
    char        cmd_buf[50] = {0};
    rt_uint32_t  server_port = 0;

    g_ftp_manager.ftp_control_socket = socket(AF_AT, SOCK_STREAM, 0); // Create a UDP socket.

    if (g_ftp_manager.ftp_control_socket < 0) {
        LOG_E("opening control socket");
        return;
    }

    /*服務器地址轉換成IP地址*/
    server = gethostbyname(FTP_SVR_ADDR); // Convert URL to IP.

    if (server == NULL) {
        LOG_E("Get ftp server hostname!");
        goto ftp_error;
    }

    // Zero out the server address structure.

    memset((char *) &serv_addr, 0, sizeof(serv_addr));

    
    /* 初始化預連接的服務端地址 */
    serv_addr.sin_family = AF_AT;
    serv_addr.sin_port = htons(FTP_SVR_PORT);
    serv_addr.sin_addr = *((struct in_addr *)server->h_addr);
    rt_memset(&(serv_addr.sin_zero), 0, sizeof(serv_addr.sin_zero));

    // Call up the server using its IP address and port number.

    if (connect(g_ftp_manager.ftp_control_socket, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) 
    {
        
        LOG_E("connecting faile");
        goto ftp_error;
    }

    /*等待服務器發來的歡迎登陸請求*/
    if((RT_EOK != ftp_wait_server_ack(g_ftp_manager.ftp_control_socket, &ack_code, 5)) || (ack_code != 220))
    {
        LOG_E("Server not ack welcome");
        goto ftp_error;
    }

    /*登陸服務器輸入用戶名*/
    sprintf(cmd_buf, "USER %s\r\n", FTP_USER_NAME);
    if((RT_EOK != ftp_send_cmd_and_server_ack(g_ftp_manager.ftp_control_socket, cmd_buf, strlen(cmd_buf), &ack_code, 5)) ||
       (ack_code != 331))
    {   
        LOG_E("Login user error!");
        goto ftp_error;
    }

    /*登陸服務器輸入密碼*/
    sprintf(cmd_buf, "PASS %s\r\n", FTP_PASSWORD);
    if((RT_EOK != ftp_send_cmd_and_server_ack(g_ftp_manager.ftp_control_socket, cmd_buf, strlen(cmd_buf), &ack_code, 5)) ||
       (ack_code != 230))
    {   
        LOG_E("Login pass error!");
        goto ftp_error;
    }

    /*切換當前工作目錄*/
    sprintf(cmd_buf, "CWD %s\r\n", FTP_SVR_PATH);
    if((RT_EOK != ftp_send_cmd_and_server_ack(g_ftp_manager.ftp_control_socket, cmd_buf, strlen(cmd_buf), &ack_code, 5)) ||
       (ack_code != 250))
    {   
        LOG_E("Switch dir error!");
        goto ftp_error;
    }

    /*切換二進制傳輸模式*/
    strcpy(cmd_buf, "TYPE I\r\n");
    if((RT_EOK != ftp_send_cmd_and_server_ack(g_ftp_manager.ftp_control_socket, cmd_buf, strlen(cmd_buf), &ack_code, 5)) ||
       (ack_code != 200))
    {   
        LOG_E("Switch dir error!");
        goto ftp_error;
    }

    /*獲取下載文件大小*/
    sprintf(cmd_buf, "SIZE %s\r\n", g_ftp_manager.file_name);
    if((RT_EOK != ftp_filesize_get(g_ftp_manager.ftp_control_socket, cmd_buf, strlen(cmd_buf), &ack_code, &g_ftp_manager.file_size, 5)) ||
       (ack_code != 213))
    {   
        LOG_E("Get filesize error!");
        goto ftp_error;
    }
    LOG_I("File size %d", g_ftp_manager.file_size);

    /*獲取服務器開放的端口號*/
    if((RT_EOK != ftp_pasv_mode(g_ftp_manager.ftp_control_socket,  &ack_code, &server_port, 5)) ||
       (ack_code != 227))
    {   
        LOG_E("PASV error!");
        goto ftp_error;
    } 

    

    /*創建數據連接*/
    g_ftp_manager.ftp_data_socket = socket(AF_AT, SOCK_STREAM, 0); 

    if (g_ftp_manager.ftp_data_socket < 0) {
        LOG_E("opening data socket");
        goto ftp_error;
    }


    /* 初始化預連接的服務端地址 */
    serv_addr.sin_family = AF_AT;
    serv_addr.sin_port = htons(server_port);
    serv_addr.sin_addr = *((struct in_addr *)server->h_addr);
    rt_memset(&(serv_addr.sin_zero), 0, sizeof(serv_addr.sin_zero));

    // Call up the server using its IP address and port number.

    if (connect(g_ftp_manager.ftp_data_socket, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) 
    {
        
        LOG_E("data connecting faile");
        goto ftp_error;
    }

    /*創建數據連接接收線程*/
    ftp_thread = rt_thread_create("ftp_data",
                                     (void (*)(void *parameter))ftp_data_thread,
                                     RT_NULL,
                                     2048,
                                     24 ,
                                     15);
    if (ftp_thread == RT_NULL)
    {
        LOG_E("Creat ftp data thread fail!");
        goto ftp_error;
    }  
    else
    {
        rt_thread_startup(ftp_thread);
    }

    /*等待數據接收線程啓動*/
    rt_thread_mdelay(1000);

    /*下載文件*/
    sprintf(cmd_buf, "RETR %s\r\n", g_ftp_manager.file_name);
    if((RT_EOK != ftp_send_cmd_and_server_ack(g_ftp_manager.ftp_control_socket, cmd_buf, strlen(cmd_buf), &ack_code, 5)) ||
       (ack_code != 150))
    {   
        LOG_E("Download file error!");
        goto ftp_error;
    }

    /*等待下載完成*/
    if((RT_EOK != ftp_wait_server_ack(g_ftp_manager.ftp_control_socket, &ack_code, 50)) || (ack_code != 226))
    {
        LOG_E("Server not ack download complete");
        
    }
    else
    {
        LOG_I("Server ack download complete");
    }
  
    
    /*退出登陸*/
    strcpy(cmd_buf, "QUIT\r\n");
    if((RT_EOK != ftp_send_cmd_and_server_ack(g_ftp_manager.ftp_control_socket, cmd_buf, strlen(cmd_buf), &ack_code, 5)) ||
       (ack_code != 221))
    {   
        LOG_E("Server not logout!");
        goto ftp_error;
    }
    


ftp_error:
    closesocket(g_ftp_manager.ftp_control_socket);
    g_ftp_manager.ftp_control_socket= -1;

    if(g_ftp_manager.ftp_data_socket != -1)
    {
        closesocket(g_ftp_manager.ftp_data_socket); 
        g_ftp_manager.ftp_data_socket = -1;
    }
    

}

    下面是ftp.h的頭文件定義。

#ifndef __FTP_H_
#define  __FTP_H_


/*********************************************************************************************************
*        定義FTP的默認用戶名稱,服務器地址,端口等
*********************************************************************************************************/
#define FTP_SVR_ADDR    "www.rXXXw-lXXXing.com"
#define FTP_SVR_PORT    21
#define FTP_SVR_PATH    "/XXXX/"
#define FTP_USER_NAME   "XXX"
#define FTP_PASSWORD    "1XXXg"
#define FTP_FILENAME    "HDRC-VC0_V10.bin"

/*********************************************************************************************************
*        定義FTP通信管理數據結構
*********************************************************************************************************/
typedef struct FTP_MANAGER_STRUCT
{
    int            ftp_control_socket;                                             /* FTP控制socket    */
    int            ftp_data_socket;                                                /* FTP數據socket    */
    char           file_name[30];                                                  /* 下載文件名稱     */
    rt_uint32_t    file_size;                                                       /* 文件大小         */
   
}FTP_MANAGER;

 

 

 

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