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