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


  接上篇:http://www.cnblogs.com/jzincnblogs/p/5217688.html,這篇主要記錄編程過程中需要注意的地方以及遇到的一些問題及解決方法。

  一、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 }

    關於兩個函數的典型用法可以參考MSDN:https://msdn.microsoft.com/en-us/library/ms738520(v=vs.85).aspx

                       https://msdn.microsoft.com/en-us/library/cc805843(v=vs.85).aspx

 

  二、換行符的問題

    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();

 


免責聲明!

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



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