FTP協議編碼原理及實現細節


一、關於sockaddr_in 、WSAData 、sockaddr等未定義的問題

1 typedef struct sockaddr_in sockaddr_in;
2 typedef struct WSAData WSAData;
3 typedef struct sockaddr sockaddr;

  在使用windows socket編程中總是遇到一系列的未定義的問題,在文件中像這樣重新再定義一次類型,基本問題都可以解決,什么沒定義就重定義什么,沒必要三個都使用。unknown type name 'sockaddr_in' 顯示未定義該類型,原因有兩個,其一是未添加ws2_32.lib庫,可以通過手動在工程的link設置里添加,其二是頭文件中只定義了該結構名稱,但是沒有定義它的別名,所以不能直接用sockaddr_in來定義類型,而需要用struct sockaddr_in定義,也可以添加別名定義 typedef struct sockaddr_in sockaddr_in; 這樣就能運行了,可參考https://www.cnblogs.com/mfrank/articles/socket_learning_001.html

二、理論知識准備

  默認情況下FTP協議使用TCP端口中的 20和21這兩個端口,其中20用於傳輸數據21用於傳輸控制信息。但是,是否使用20作為傳輸數據的端口與FTP使用的傳輸模式有關,如果采用主動模式,那么數據傳輸端口就是20;如果采用被動模式,則具體最終使用哪個端口要服務器端和客戶端協商決定

  FTP支持兩種模式,一種方式叫做Standard (也就是 PORT方式,主動方式),一種是 Passive(也就是PASV,被動方式)。 Standard模式 FTP的客戶端發送 PORT 命令到FTP服務器。Passive模式FTP的客戶端發送 PASV命令到 FTP Server。下面介紹一下這兩種方式的工作原理:

  Port: FTP 客戶端首先和FTP服務器的TCP 21端口建立連接,通過這個通道發送命令,客戶端需要接收數據的時候在這個通道上發送PORT命令。 PORT命令包含了客戶端用什么端口接收數據。在傳送數據的時候,服務器端通過自己的TCP 20端口連接至客戶端的指定端口發送數據。 FTP server必須和客戶端建立一個新的連接用來傳送數據。

  Passive:在建立控制通道的時候和Standard模式類似,但建立連接后發送的不是Port命令,而是Pasv命令。FTP服務器收到Pasv命令后,隨機打開一個高端端口(端口號大於1024)並且通知客戶端在這個端口上傳送數據的請求,客戶端連接FTP服務器此端口,通過三次握手建立通道,然后FTP服務器將通過這個端口進行數據的傳送。

  很多防火牆在設置的時候都是不允許接受外部發起的連接的,所以許多位於防火牆后或內網的FTP服務器不支持PASV模式,因為客戶端無法穿過防火牆打開FTP服務器的高端端口;而許多內網的客戶端不能用PORT模式登陸FTP服務器,因為從服務器的TCP 20無法和內部網絡的客戶端建立一個新的連接,造成無法工作 參考網絡相關文章:

1. FTP協議完全詳解

  介紹基礎知識,並列舉常用命令,引用網址 https://www.cnblogs.com/duanxz/p/5120960.html

2. FTP常見命令詳解

  在window下按window + r可打開DOS命令窗口,然后就可以輸入FTP命令了。轉載自:https://blog.csdn.net/qq_38526635/article/details/82147980?utm_medium=distribute.pc_relevant.none-task-blog-searchFromBaidu-1.not_use_machine_learn_pai&depth_1-utm_source=distribute.pc_relevant.none-task-blog-searchFromBaidu-1.not_use_machine_learn_pai

  1>. 登錄FTP服務器

  方法一:直接輸入ftp加ip地址ftp 192.168.10.xxx
  方法二:直接輸入ftp,進入ftp服務后輸入open加ip地址open 192.168.10.xxx
  當連接成功后會讓你進行身份驗證,在輸入密碼時屏幕上沒有任何顯示,不用管,直接輸完密碼敲回車鍵即可。

  2>. 查看FTP服務器上的文件

  (一般情況下用戶都會被限制目錄的訪問權限,只可在當前目錄下進行操作)
  dir:顯示服務器目錄和文件列表
  ls:顯示服務器簡易的文件列表
  cd:進入服務器指定的目錄

  dir命令可以使用通配符“”和“?”,比如,顯示當前目錄中所有擴展名為jpg的文件,可使用命令 dir .jpg。

  cd命令中必須帶目錄名。比如 cd main 表示進入當前目錄下的main子目錄

  3>. 下載文件

  上傳和下載文件時應該使用正確的傳輸類型,FTP的傳輸類型分為ASCII碼方式和二進制方式兩種,對.txt、.htm等文件應采用ASCII碼方式傳輸,對.exe或圖片、視頻、音頻等文件應采用二進制方式傳輸。在默認情況下,FTP為ASCII碼傳輸方式。
  type:查看當前的傳輸方式
  ascii:設定傳輸方式為ASCII碼方式
  binary:設定傳輸方式為二進制方式
  (以上命令都不帶參數)
  get:下載指定文件get filename [newname] (filename為下載的FTP服務器上的文件名,newname為保存在本都計算機上時使用的名字,如果不指定newname,文件將以原名保存。

  get命令下載的文件將保存在本地計算機的工作目錄下。該目錄是啟動FTP時在盤符C:后顯示的目錄。如果想修改本地計算機的工作目錄,可以使用 lcd 命令。比如:lcd d:\ 表示將工作目錄設定為D盤的根目錄。

  mget:下載多個文件mget filename [filename ....](mget命令支持通配符“”和“?”,比如:mget .mp3 表示下載FTP服務器當前目錄下的所有擴展名為mp3的文件。)

  4>. 上傳文件

  put:上傳指定文件put filename [newname]
  send:上傳指定文件send filename [newname]
  (filename為上傳的本地文件名,newname為上傳至FTP服務器上時使用的名字,如果不指定newname,文件將以原名上傳。)

  上傳文件前,應該根據文件的類型設置傳輸方式,本機的工作目錄也應該設置為上傳文件所在的目錄。

  這里的send和put方法用法都基本相同,但是上傳速度send卻要比put快很多,有興趣的人可以去研究下。

  5>. 結束並退出FTP

  close:結束與服務器的FTP會話
  quit:結束與服務器的FTP會話並退出FTP環境

  6>. 其它FTP命令

  pwd:查看FTP服務器上的當前工作目錄
  rename [filename] [newfilename]:重命名FTP服務器上的文件
  delete [filename]:刪除FTP服務器上的文件
  help[cmd]:顯示FTP命令的幫助信息,cmd是命令名,如果不帶參數,則顯示所有FTP命令

3. ftp控制台常見問題

  1>、ftp的get命令和mget命令有何不同?

  get一次只下載一個文件;mget一次可以下載多個文件,而且支持通配符,需要注意的是在mget的時侯,需要對每一個文件都選擇y/n,如果想不交互的下載全部的文件,可以先用prompt命令關掉交互方式(關閉:prompt off;打開:prompt on)。

  2>、FTP使用什么命令來定位服務器與本地硬盤的路徑?

  ftp中用lcd切換本地路徑,用cd切換遠程服務器的路徑。常用到的命令如下:

cd目錄名(進入服務器目錄)    lcd目錄名(進入本機目錄)

cd \(退到服務器根目錄)      lcd \(退到本機根目錄)

cd ..(退回到上一級目錄)     lcd ..(退回到上一級目錄)

  3>、!命令有何作用?

  執行本地shell命令,如:!dir(顯示本機當親目錄內容),如果不加!如:dir(顯示服務器當前目錄內容)

  4>、ftp命令支持“含有空格”的文件夾/文件名嗎?

  支持,只要在引用時加上雙引號“”即可!

三、實現細節

1. 使用 Socket 通信實現 FTP 客戶端程序

  轉載自https://www.ibm.com/developerworks/cn/linux/l-cn-socketftp/#ibm-pcon

FTP 概述

  文件傳輸協議(FTP)作為網絡共享文件的傳輸協議,在網絡應用軟件中具有廣泛的應用。FTP的目標是提高文件的共享性和可靠高效地傳送數據。

  在傳輸文件時,FTP 客戶端程序先與服務器建立連接,然后向服務器發送命令。服務器收到命令后給予響應,並執行命令。FTP 協議與操作系統無關,任何操作系統上的程序只要符合 FTP 協議,就可以相互傳輸數據。本文主要基於 LINUX 平台,對 FTP 客戶端的實現原理進行詳盡的解釋並闡述如何使用 C 語言編寫一個簡單的 FTP 客戶端。

FTP 協議

  相比其他協議,如 HTTP 協議,FTP 協議要復雜一些。與一般的 C/S 應用不同點在於一般的C/S 應用程序一般只會建立一個 Socket 連接,這個連接同時處理服務器端和客戶端的連接命令和數據傳輸。而FTP協議中將命令與數據分開傳送的方法提高了效率。

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

命令端口

  一般來說,客戶端有一個 Socket 用來連接 FTP 服務器的相關端口,它負責 FTP 命令的發送和接收返回的響應信息。一些操作如“登錄”、“改變目錄”、“刪除文件”,依靠這個連接發送命令就可完成。

數據端口

  對於有數據傳輸的操作,主要是顯示目錄列表,上傳、下載文件,我們需要依靠另一個 Socket來完成。

  如果使用被動模式,通常服務器端會返回一個端口號。客戶端需要用另開一個 Socket 來連接這個端口,然后我們可根據操作來發送命令,數據會通過新開的一個端口傳輸。

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

  下面對 FTP 的主動模式和被動模式做一個簡單的介紹。

主動模式 (PORT)

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

  FTP 的客戶端只是告訴服務器自己的端口號,讓服務器來連接客戶端指定的端口。對於客戶端的防火牆來說,這是從外部到內部的連接,可能會被阻塞。

被動模式 (PASV)

  為了解決服務器發起到客戶的連接問題,有了另一種 FTP 連接方式,即被動方式。命令連接和數據連接都由客戶端發起,這樣就解決了從服務器到客戶端的數據端口的連接被防火牆過濾的問題。

  被動模式下,當開啟一個 FTP 連接時,客戶端打開兩個任意的本地端口 (N > 1024 和 N+1) 。

  第一個端口連接服務器的 21 端口,提交 PASV 命令。然后,服務器會開啟一個任意的端口 (P > 1024 ),返回如“227 entering passive mode (127,0,0,1,4,18)”。 它返回了 227 開頭的信息,在括號中有以逗號隔開的六個數字,前四個指服務器的地址,最后兩個,將倒數第二個乘 256 再加上最后一個數字,這就是 FTP 服務器開放的用來進行數據傳輸的端口。如得到 227 entering passive mode (h1,h2,h3,h4,p1,p2),那么端口號是 p1*256+p2,ip 地址為h1.h2.h3.h4。這意味着在服務器上有一個端口被開放。客戶端收到命令取得端口號之后, 會通過 N+1 號端口連接服務器的端口 P,然后在兩個端口之間進行數據傳輸。

主要用到的 FTP 命令

  FTP 每個命令都有 3 到 4 個字母組成,命令后面跟參數,用空格分開。每個命令都以 "\r\n"結束。

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

USER: 指定用戶名。通常是控制連接后第一個發出的命令。“USER gaoleyi\r\n”: 用戶名為gaoleyi 登錄。

PASS: 指定用戶密碼。該命令緊跟 USER 命令后。“PASS gaoleyi\r\n”:密碼為 gaoleyi。

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

CWD: 改變工作目錄。如:“CWD dirname\r\n”。

PASV: 讓服務器在數據端口監聽,進入被動模式。如:“PASV\r\n”。

PORT: 告訴 FTP 服務器客戶端監聽的端口號,讓 FTP 服務器采用主動模式連接客戶端。如:“PORT h1,h2,h3,h4,p1,p2”。

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

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

REST: 該命令並不傳送文件,而是略過指定點后的數據。此命令后應該跟其它要求文件傳輸的 FTP 命令。“REST 100\r\n”:重新指定文件傳送的偏移量為 100 字節。

QUIT: 關閉與服務器的連接。

FTP 響應碼

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

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

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

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

第三個數字提供了更加詳細的信息。

第一個數字的含義如下:

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

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

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

4 表示信息暫時錯誤。

5 表示信息永久錯誤。

第二個數字的含義如下:

0 表示語法。

1 表示系統狀態和信息。

2 表示連接狀態。

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

4 表示未定義。

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

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

實現 FTP 客戶端上傳下載功能

下面讓我們通過一個例子來對 FTP 客戶端有一個深入的了解。本文實現的 FTP 客戶端有下列功能:

  1. 客戶端和 FTP 服務器建立 Socket 連接。
  2. 向服務器發送 USER、PASS 命令登錄 FTP 服務器。
  3. 使用 PASV 命令得到服務器監聽的端口號,建立數據連接。
  4. 使用 RETR/STOR 命令下載/上傳文件。
  5. 在下載完畢后斷開數據連接並發送 QUIT 命令退出。

本例中使用的 FTP 服務器為 filezilla。在整個交互的過程中,控制連接始終處於連接的狀態,數據連接在每傳輸一個文件時先打開,后關閉。

客戶端和 FTP 服務器建立 Socket 連接

當客戶端與服務器建立連接后,服務器會返回 220 的響應碼和一些歡迎信息。

圖 1. 客戶端連接到服務器端

圖 1. 客戶端連接到服務器端

清單 1. 客戶端連接到 FTP 服務器,接收歡迎信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SOCKET control_sock;
struct hostent *hp;
struct sockaddr_in server;
memset(&server, 0, sizeof(struct sockaddr_in));
 
/* 初始化socket */
control_sock = socket(AF_INET, SOCK_STREAM, 0);
hp = gethostbyname(server_name);
memcpy(&server.sin_addr, hp->h_addr, hp->h_length);
server.sin_family = AF_INET;
server.sin_port = htons(port);
 
/* 連接到服務器端 */
connect(control_sock,(struct sockaddr *)&server, sizeof(server));
/* 客戶端接收服務器端的一些歡迎信息 */
read(control_sock, read_buf, read_len);

客戶端登錄 FTP 服務器

當客戶端發送用戶名和密碼,服務器驗證通過后,會返回 230 的響應碼。然后客戶端就可以向服務器端發送命令了。

圖 2. 客戶端登錄 FTP 服務器

客戶端登錄 FTP 服務器

清單 2. 客戶端發送用戶名和密碼,登入 FTP 服務器
1
2
3
4
5
6
7
8
9
10
11
12
13
/* 命令 ”USER username\r\n” */
sprintf(send_buf,"USER %s\r\n",username);
/*客戶端發送用戶名到服務器端 */
write(control_sock, send_buf, strlen(send_buf));
/* 客戶端接收服務器的響應碼和信息,正常為 ”331 User name okay, need password.” */
read(control_sock, read_buf, read_len);
 
/* 命令 ”PASS password\r\n” */
sprintf(send_buf,"PASS %s\r\n",password);
/* 客戶端發送密碼到服務器端 */
write(control_sock, send_buf, strlen(send_buf));
/* 客戶端接收服務器的響應碼和信息,正常為 ”230 User logged in, proceed.” */
read(control_sock, read_buf, read_len);

客戶端讓 FTP 服務器進入被動模式

當客戶端在下載/上傳文件前,要先發送命令讓服務器進入被動模式。服務器會打開數據端口並監聽。並返回響應碼 227 和數據連接的端口號。

圖 3. 客戶端讓服務器進入被動模式

圖 3. 客戶端讓服務器進入被動模式

清單 3. 讓服務器進入被動模式,在數據端口監聽
1
2
3
4
5
6
7
/* 命令 ”PASV\r\n” */
sprintf(send_buf,"PASV\r\n");
/* 客戶端告訴服務器用被動模式 */
write(control_sock, send_buf, strlen(send_buf));
/*客戶端接收服務器的響應碼和新開的端口號,
* 正常為 ”227 Entering passive mode (< h1 ,h2,h3,h4,p1,p2>)” */
read(control_sock, read_buf, read_len);

客戶端通過被動模式下載文件

當客戶端發送命令下載文件。服務器會返回響應碼 150,並向數據連接發送文件內容。

圖 4. 客戶端從FTP服務器端下載文件

圖 4. 客戶端從FTP服務器端下載文件

清單 4. 客戶端連接到 FTP 服務器的數據端口並下載文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* 連接服務器新開的數據端口 */
connect(data_sock,(struct sockaddr *)&server, sizeof(server));
/* 命令 ”CWD dirname\r\n” */
sprintf(send_buf,"CWD %s\r\n", dirname);
/* 客戶端發送命令改變工作目錄 */
write(control_sock, send_buf, strlen(send_buf));
/* 客戶端接收服務器的響應碼和信息,正常為 ”250 Command okay.” */
read(control_sock, read_buf, read_len);
 
/* 命令 ”SIZE filename\r\n” */
sprintf(send_buf,"SIZE %s\r\n",filename);
/* 客戶端發送命令從服務器端得到下載文件的大小 */
write(control_sock, send_buf, strlen(send_buf));
/* 客戶端接收服務器的響應碼和信息,正常為 ”213 < size >” */
read(control_sock, read_buf, read_len);
 
/* 命令 ”RETR filename\r\n” */
sprintf(send_buf,"RETR %s\r\n",filename);
/* 客戶端發送命令從服務器端下載文件 */
write(control_sock, send_buf, strlen(send_buf));
/* 客戶端接收服務器的響應碼和信息,正常為 ”150 Opening data connection.” */
read(control_sock, read_buf, read_len);
 
/* 客戶端創建文件 */
file_handle = open(disk_name, CRFLAGS, RWXALL);
for( ; ; ) {
... ...
/* 客戶端通過數據連接 從服務器接收文件內容 */
read(data_sock, read_buf, read_len);
/* 客戶端寫文件 */
write(file_handle, read_buf, read_len);
... ...
}
/* 客戶端關閉文件 */
rc = close(file_handle);

客戶端退出服務器

當客戶端下載完畢后,發送命令退出服務器,並關閉連接。服務器會返回響應碼 200。

圖 5. 客戶端從 FTP 服務器退出

圖 5. 客戶端從 FTP 服務器退出

清單 5. 客戶端關閉數據連接,退出 FTP 服務器並關閉控制連接
1
2
3
4
5
6
7
8
9
10
11
12
13
/* 客戶端關閉數據連接 */
close(data_sock);
/* 客戶端接收服務器的響應碼和信息,正常為 ”226 Transfer complete.” */
read(control_sock, read_buf, read_len);
 
/* 命令 ”QUIT\r\n” */
sprintf(send_buf,"QUIT\r\n");
/* 客戶端將斷開與服務器端的連接 */
write(control_sock, send_buf, strlen(send_buf));
/* 客戶端接收服務器的響應碼,正常為 ”200 Closes connection.” */
read(control_sock, read_buf, read_len);
/* 客戶端關閉控制連接 */
close(control_sock);

至此,下載文件已經完成。需要注意的是發送 FTP 命令的時候,在命令后要緊跟 “\r\n”,否則服務器不會返回信息。回車換行符號 “\r\n” 是 FTP 命令的結尾符號,當服務器接收到這個符號時,認為客戶端發送的命令已經結束,開始處理。否則會繼續等待。

讓我們來看一下 FTP 服務器這一端的響應情況:

清單 6. 客戶端下載文件時,FTP 服務器的響應輸出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(not logged in) (127.0.0.1)> Connected, sending welcome message...
(not logged in) (127.0.0.1)> 220-FileZilla Server version 0.9.36 beta
(not logged in) (127.0.0.1)> 220 hello gaoleyi
(not logged in) (127.0.0.1)> USER gaoleyi
(not logged in) (127.0.0.1)> 331 Password required for gaoleyi
(not logged in) (127.0.0.1)> PASS *********
gaoleyi (127.0.0.1)> 230 Logged on
gaoleyi (127.0.0.1)> PWD
gaoleyi (127.0.0.1)> 257 "/" is current directory.
gaoleyi (127.0.0.1)> SIZE file.txt
gaoleyi (127.0.0.1)> 213 4096
gaoleyi (127.0.0.1)> PASV
gaoleyi (127.0.0.1)> 227 Entering Passive Mode (127,0,0,1,13,67)
gaoleyi (127.0.0.1)> RETR file.txt
gaoleyi (127.0.0.1)> 150 Connection accepted
gaoleyi (127.0.0.1)> 226 Transfer OK
gaoleyi (127.0.0.1)> QUIT
gaoleyi (127.0.0.1)> 221 Goodbye

首先,服務器准備就緒后返回 220。客戶端接收到服務器端返回的響應碼后,相繼發送“USER username” 和 “PASS password” 命令登錄。隨后,服務器返回的響應碼為 230 開頭,說明客戶端已經登入了。這時,客戶端發送 PASV 命令讓服務器進入被動模式。服務器返回如 “227 Entering Passive Mode (127,0,0,1,13,67)”,客戶端從中得到端口號,然后連接到服務器的數據端口。接下來,客戶端發送下載命令,服務器會返回響應碼 150,並從數據端口發送數據。最后,服務器返回 “226 transfer complete”,表明數據傳輸完成。

需要注意的是,客戶端不要一次發送多條命令,例如我們要打開一個目錄並且顯示這個目錄,我們得發送 CWD dirname,PASV,LIST。在發送完 CWD dirname 之后等待響應代碼,然后再發送后面一條。當 PASV 返回之后,我們打開另一個 Socket 連接到相關端口上。然后發送 LIST,返回 125 之后在開始接收數據,最后返回 226 表明完成。

在傳輸多個文件的過程中,需要注意的是每次新的傳輸都必須重新使用 PASV 獲取新的端口號,接收完數據后應該關閉該數據連接,這樣服務器才會返回一個 2XX 成功的響應。然后客戶端可以繼續下一個文件的傳輸。

上傳文件與下載文件相比,登入驗證和切換被動模式都如出一轍,只需要改變發送到服務器端的命令,並通過數據連接發送文件內容。

客戶端通過被動模式向服務器上傳文件

當客戶端發送命令上傳文件,服務器會從數據連接接收文件。

圖 6. 客戶端連接到 FTP 服務器的數據端口並上傳文件

客戶端連接到 FTP 服務器的數據端口並上傳文件

客戶端通過主動模式向服務器上傳文件

到目前為止,本文介紹的都是客戶端用被動模式進行文件的上傳和下載。下面將介紹客戶端用主動模式下載文件。

圖 7. 用主動模式從 FTP 服務器下載文件

用主動模式從 FTP 服務器下載文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
清單 7. 用主動模式從 FTP 服務器下載文件的示例 C 程序
... ...
SOCKET data_sock;
data_sock = socket(AF_INET, SOCK_STREAM, 0);
struct  sockaddr_in  name;
name.sin_family = AF_INET;
name.sin_addr.s_addr = htons(INADDR_ANY);
server_port = p1*256+p2;
length = sizeof(name);
name.sin_port = htons(server_port);
bind(server_sock, (struct sockaddr *)&name, length);
struct  sockaddr_in client_name;
length = sizeof(client_name);
 
/* 客戶端開始監聽端口p1*256+p2 */
listen(server_sock, 64);
 
/* 命令 ”PORT \r\n” */
sprintf(send_buf,"PORT 1287,0,0,1,%d,%d\r\n", p1, p2);
write(control_sock, send_buf,strlen(send_buf));
/* 客戶端接收服務器的響應碼和信息,正常為 ”200 Port command successful” */
read(control_sock, read_buf, read_len);
 
sprintf(send_buf,"RETR filename.txt\r\n");
write(control_sock, send_buf, strlen(send_buf));
/* 客戶端接收服務器的響應碼和信息,正常為 ”150 Opening data channel for file transfer.” */
read(control_sock, read_buf, read_len);
 
/* ftp客戶端接受服務器端的連接請求 */
data_sock = accept(server_sock,(struct sockaddr *)&client_name, &length);
... ...
 
file_handle = open(disk_name, ROFLAGS, RWXALL);
for( ; ; ) {
... ...
read(data_sock, read_buf, read_len);
write(file_handle, read_buf, read_len);
... ...
}
close(file_handle);

客戶端通過 PORT 命令告訴服務器連接自己的 p1*256+p2 端口。隨后在這個端口進行監聽,等待 FTP 服務器連接上來, 再通過這個數據端口來傳輸文件。PORT 方式在傳送數據時,FTP 客戶端其實就相當於一個服務器端,由 FTP 服務器主動連接自己。

斷點續傳

由於網絡不穩定,在傳輸文件的過程中,可能會發生連接斷開的情況,這時候需要客戶端支持斷點續傳的功能,下次能夠從上次終止的地方開始接着傳送。需要使用命令 REST。如果在斷開連接前,一個文件已經傳輸了 512 個字節。則斷點續傳開始的位置為 512,服務器會跳過傳輸文件的前 512 字節。

清單 8. 從 FTP 服務器斷點續傳下載文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
... ...
/* 命令 ”REST offset\r\n” */
sprintf(send_buf,"REST %ld\r\n", offset);
/* 客戶端發送命令指定下載文件的偏移量 */
write(control_sock, send_buf, strlen(send_buf));
/* 客戶端接收服務器的響應碼和信息,
*正常為 ”350 Restarting at < position >. Send STORE or RETRIEVE to initiate transfer.” */
read(control_sock, read_buf, read_len);
... ...
 
/* 命令 ”RETR filename\r\n” */
sprintf(send_buf,"RETR %s\r\n",filename);
/* 客戶端發送命令從服務器端下載文件, 並且跳過該文件的前offset字節*/
write(control_sock, send_buf, strlen(send_buf));
/* 客戶端接收服務器的響應碼和信息,*
*正常為 ”150 Connection accepted, restarting at offset < position >” */
read(control_sock, read_buf, read_len);
... ...
 
file_handle = open(disk_name, CRFLAGS, RWXALL);
/* 指向文件寫入的初始位置 */
lseek(file_handle, offset, SEEK_SET);
... ...

結束語

  本文從應用實現的角度,介紹了 FTP 協議。並用詳盡的例子分析了如何用主動模式和被動模式實現 FTP 客戶端上傳下載文件,如何進行斷點續傳。通過本文可以讓讀者對 FTP 客戶端的原理有一個深入的了解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM