接上篇:,这篇主要记录编程过程中需要注意的地方以及遇到的一些问题及解决方法。
一、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:
二、换行符的问题
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();