1. 程式人生 > >使用c++實現一個FTP客戶端(三)

使用c++實現一個FTP客戶端(三)

  一、gethostbyname(),inet_ntoa()等函式已經過時

    使用上面兩個函式時編譯器會報錯並提示函式已經是過時的了(obsolete),應該用getaddrinfo()與InetNtop()代替,這兩個函式都是協議無關的,同時支援IPv4和IPv6,下面是一個使用例子:

複製程式碼
 1 string GetIPAddress(int af)
 2 {
 3     char host_name[IP_SIZE];
 4     char buf_ip[IP_SIZE];
 5     //
 6     addrinfo hints;
 7     memset(&hints, 0
, sizeof(addrinfo)); 8 hints.ai_family = af; 9 hints.ai_socktype = SOCK_STREAM; 10 hints.ai_protocol = IPPROTO_TCP; 11 // 12 addrinfo *result = nullptr; 13 //獲取主機名字 14 int ret_val = ::gethostname(host_name, IP_SIZE); 15 if (ret_val == SOCKET_ERROR) 16 { 17 cerr << "
Failed to get host name!\n"; 18 return ""; 19 } 20 //通過主機名字獲取ip地址 21 ret_val = ::getaddrinfo(host_name, nullptr, &hints, &result); 22 if (ret_val != 0) 23 { 24 cerr << "Failed tp get host by name!\n"; 25 return ""; 26 } 27 SOCKADDR_IN *addr = (SOCKADDR_IN*)result->ai_addr;
28 ::InetNtop(af, &addr->sin_addr, buf_ip, IP_SIZE); 29 //釋放地址資源 30 ::freeaddrinfo(result); 31 return (string)buf_ip; 32 }
複製程式碼

  二、換行符的問題

    c++中如果輸出時需要換行可以使用\n,但需要注意的是,在windows中回車換行表示為\r\n,而linux中表示為\n,而這也是FTP協議中二進位制模式與ASCII模式的區別之一:ASCII模式會對檔案進行轉換,將換行符轉換為客戶端系統的表示方法,而二進位制模式則不對檔案進行改動。所以在windows環境下,FTP客戶端與伺服器互動過程中,客戶端傳送命令時要以\r\n結尾,而接收伺服器的多行資料時每行資料的換行符均為\r\n。

  三、被動模式

    在FTP客戶端與伺服器進行資料傳輸時,一般使用被動模式,而客戶端與伺服器的資料連線在每次傳輸完成後都會關閉,這意味著每次客戶端與伺服器傳輸資料前都要先建立資料連線,也就意味著每次都要重新進入被動模式。通過傳送PASV命令可以請求進入被動模式,若進入成功,伺服器返回一條形如 227 Entering Passive Mode (a,b,c,d,e,f). 的訊息,其中a.b.c.d表示伺服器的IP地址,通過e,f可計算得到客戶端應連線的伺服器埠號,計算公式為:埠號=e*256 + f。

  四、斷點續傳

    當客戶端下載檔案過程因種種原因中斷後,下次啟動下載時就要用到斷點續傳,避免重新下載。

    斷點續傳的實現步驟如下:

      1.呼叫Windows API函式CreateFile()開啟檔案,然後使用GetFileSize()獲取已下載的位元組數。

      2.從伺服器中獲取目標檔案的位元組數,進行比較。

      3.斷點續傳的開始位置為已下載的位元組數加1。

      4.傳送命令“REST offset\r\n”,其中offset為計算出來的檔案偏移量。

      5.若伺服器響應成功,則傳送命令“RETR 檔名\r\n”,若響應成功,檔案開始斷點續傳。

    斷點續傳關鍵部分程式碼如下:

複製程式碼
 1         HANDLE h_file = ::CreateFile(str_path.c_str(), 0, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
 2         if (h_file == INVALID_HANDLE_VALUE)
 3         {
 4             return false;
 5         }
 6         int dld_size = ::GetFileSize(h_file, nullptr);
 7         ::CloseHandle(h_file);
 8         //
 9         int file_size = GetFileInfo(f).GetSize();
10         if (file_size == dld_size)
11         {
12             cout << "File already downloaded!\n";
13             return false;
14         }
15         //
16         int read_start = dld_size + 1;
17         file.open(str_path, fstream::out | fstream::app);
18         //
19         char buf_num[32];
20         memset(buf_num, 0, sizeof(buf_num));
21         _itoa_s(read_start, buf_num, sizeof(buf_num), 10);
22         string cmd_dld = "REST ";
23         cmd_dld += buf_num;
24         cmd_dld += "\r\n";
25         //
26         EnterPasvMode();
27         //
28         logger.SendCmd(cmd_dld);
29         logger.RecvResponse();
30         if (logger.GetLastLog().substr(0, 3) == "500")
31         {
32             cerr << "File name incorrect!\n";
33             return false;
34         }
35         //
36         cmd_dld = "RETR " + f + "\r\n";
37         logger.SendCmd(cmd_dld);
38         logger.RecvResponse();
39         //
40         while (::recv(sock_data, dld_file, FILE_SIZE, 0) != 0)
41         {
42             cout << strlen(dld_file) << "\n";
43             file << dld_file;
44             memset(dld_file, 0, FILE_SIZE);
45         }
46         file.close();
47         sock_data.Close();