基于WinInet的FTP文件下载实现

Nightfall

发布日期: 2018-12-09 14:46:21 浏览量: 960
评分:
star star star star star star star star star_border star_border
*转载请注明来自write-bug.com

背景

对于在网络之间的文件传输,我们通常使用FTP传输协议。因为,FTP就是专门为了文件传输而生的,传输效率高,稳定性好。所以,FTP文件传输协议,是我们网络传输中常用的协议。

为了学习这方面的开发知识,自己专门写了个使用Windows提供的WinInet库实现了FTP的文件传输的基本功能。现在,我就把基于WinInet库实现的FTP文件下载和FTP文件上传分成两个文档分别进行解析。本文介绍的是基于WinInet的FTP文件下载的实现。

函数介绍

介绍FTP下载文件使用到的主要的WinInet库中的API函数。

1. InternetOpen 介绍

初始化一个应用程序,以使用 WinINet 函数。

函数声明

  1. HINTERNET InternetOpen(
  2. In LPCTSTR lpszAgent,
  3. In DWORD dwAccessType,
  4. In LPCTSTR lpszProxyName,
  5. In LPCTSTR lpszProxyBypass,
  6. In DWORD dwFlags
  7. );

参数
lpszAgent
指向一个空结束的字符串,该字符串指定调用WinInet函数的应用程序或实体的名称。使用此名称作为用户代理的HTTP协议。
dwAccessType
指定访问类型,参数可以是下列值之一:

Value Meaning
INTERNET_OPEN_TYPE_DIRECT 使用直接连接网络
INTERNET_OPEN_TYPE_PRECONFIG 获取代理或直接从注册表中的配置,使用代理连接网络
INTERNETOPEN_TYPE_PRECONFIG WITH_NO_AUTOPROXY 获取代理或直接从注册表中的配置,并防止启动Microsoft JScript或Internet设置(INS)文件的使用
INTERNET_OPEN_TYPE_PROXY 通过代理的请求,除非代理旁路列表中提供的名称解析绕过代理,在这种情况下,该功能的使用

lpszProxyName
指针指向一个空结束的字符串,该字符串指定的代理服务器的名称,不要使用空字符串;如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY,则此参数应该设置为NULL。

lpszProxyBypass
指向一个空结束的字符串,该字符串指定的可选列表的主机名或IP地址。如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY的 ,参数省略则为NULL。

dwFlags
参数可以是下列值的组合:

VALUE MEANING
INTERNET_FLAG_ASYNC 使异步请求处理的后裔从这个函数返回的句柄
INTERNET_FLAG_FROM_CACHE 不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND
INTERNET_FLAG_OFFLINE 不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND

返回值
成功:返回一个有效的句柄,该句柄将由应用程序传递给接下来的WinInet函数。
失败:返回NULL。

2. InternetConnect 介绍

建立 Internet 的连接。

函数声明

  1. HINTERNET WINAPI InternetConnect(
  2. HINTERNET hInternet,
  3. LPCTSTR lpszServerName,
  4. INTERNET_PORT nServerPort,
  5. LPCTSTR lpszUserName,
  6. LPCTSTR lpszPassword,
  7. DWORD dwService,
  8. DWORD dwFlags,
  9. DWORD dwContext
  10. );

参数说明
hInternet:由InternetOpen返回的句柄。
lpszServerName:连接的ip或者主机名
nServerPort:连接的端口。
lpszUserName:用户名,如无置NULL。
lpszPassword:密码,如无置NULL。
dwService:使用的服务类型,可以使用以下

  • INTERNET_SERVICE_FTP = 1:连接到一个 FTP 服务器上
  • INTERNET_SERVICE_GOPHER = 2
  • INTERNET_SERVICE_HTTP = 3:连接到一个 HTTP 服务器上

dwFlags:文档传输形式及缓存标记。一般置0。
dwContext:当使用回叫信号时, 用来识别应用程序的前后关系。
返回值
成功返回非0。如果返回0。要InternetCloseHandle释放这个句柄。

3. FtpOpenFile介绍

启动访问FTP服务器上的远程文件以进行读取或写入。

函数声明

  1. HINTERNET FtpOpenFile(
  2. _In_ HINTERNET hConnect,
  3. _In_ LPCTSTR lpszFileName,
  4. _In_ DWORD dwAccess,
  5. _In_ DWORD dwFlags,
  6. _In_ DWORD_PTR dwContext
  7. );

参数

  • hConnect [in]
    处理FTP会话。

  • lpszFileName [in]
    指向包含要访问的文件名称的以NULL结尾的字符串。

  • dwAccess [in]
    文件访问。 该参数可以是GENERIC_READ或GENERIC_WRITE,但不能同时使用。

  • dwFlags [in]
    转移发生的条件。 应用程序应选择一种传输类型,以及指示文件缓存如何被控制的任何标志。传输类型可以是以下值之一。

VALUE MEANING
FTP_TRANSFER_TYPE_ASCII 使用FTP的ASCII(类型A)传输方法传输文件。 控制和格式化信息被转换为本地等价物。
FTP_TRANSFER_TYPE_BINARY 使用FTP的图像(类型I)传输方法传输文件。 文件完全按照存在的方式进行传输,没有任何变化。 这是默认的传输方式。
FTP_TRANSFER_TYPE_UNKNOWN 默认为FTP_TRANSFER_TYPE_BINARY。
INTERNET_FLAG_TRANSFER_ASCII 以ASCII格式传输文件。
INTERNET_FLAG_TRANSFER_BINARY 将文件作为二进制文件传输。

以下值用于控制文件的缓存。 应用程序可以使用这些值中的一个或多个。

VALUE MEANING
INTERNET_FLAG_HYPERLINK 在确定是否从网络重新加载项目时,如果没有到期时间并且没有LastModified时间从服务器返回,则强制重新加载。
INTERNET_FLAG_NEED_FILE 如果无法缓存文件,则导致创建临时文件。
INTERNET_FLAG_RELOAD 强制从源服务器下载所请求的文件,对象或目录列表,而不是从缓存中下载。
INTERNET_FLAG_RESYNCHRONIZE 如果资源自上次下载以来已被修改,请重新加载HTTP资源。 所有FTP资源都被重新加载。
  • dwContext [in]
    指向包含将此搜索与任何应用程序数据相关联的应用程序定义值的变量。 这仅在应用程序已经调用InternetSetStatusCallback来设置状态回调函数时才会使用。

返回值

  • 如果成功则返回一个句柄,否则返回NULL。 要检索特定的错误消息,请调用GetLastError。

4. InternetReadFile 介绍

函数声明

  1. BOOL InternetReadFile( __in HINTERNET hFile,
  2. __out LPVOID lpBuffer,
  3. __in DWORD dwNumberOfBytesToRead,
  4. __out LPDWORD lpdwNumberOfBytesRead
  5. );

参数

  • hFile[in]

    由InternetOpenUrl,FtpOpenFile, 或HttpOpenRequest函数返回的句柄.

  • lpBuffer[out]

    缓冲器指针

  • dwNumberOfBytesToRead[in]

    欲读数据的字节量。

  • lpdwNumberOfBytesRead[out]

    接收读取字节量的变量。该函数在做任何工作或错误检查之前都设置该值为零

返回值
成功:返回TRUE,失败,返回FALSE

实现原理

首先,我们先介绍下FTP的URL格式:

  1. FTP://账号:密码@主机/子目录或文件

例如:ftp://admin:123456@192.168.0.1/mycode/520.zip

其中,“FTP”就表示使用FTP传输数据;“账号”即登录FTP服务器的用户名;“密码”即登录FTP服务器用户名对应的密码;“主机”表示服务器的IP地址;“子目录或文件”即要进行操作的文件或目录的路径。

在,WinInet库中,提供了InternetCrackUrl这个函数专门用于URL的分解,在我写的《URL分解之InternetCrackUrl》则篇文章中有详细介绍和使用方法。

那么,基于WinInet库的FTP文件下载的原理如下所示:

  • 首先,我们先调用对URL进行分解,从URL中获取后续操作需要的信息。

  • 然后,使用InternetOpen初始化WinInet库,建立网络会话。

  • 接着,使用InternetConnect与服务器建立连接,并设置FTP数据传输方式。

  • 之后,我们就可以调用FtpOpenFile函数,根据文件路径,以GENERIC_READ的方式,打开文件并获取服务器上文件的句柄。

  • 接着,根据文件句柄,调用FtpGetFileSize获取文件的大小,并根据文件大小在程序申请一块动态内存,以便存储下载的数据。

  • 然后,就可以调用InternetReadFile从服务器上下载文件数据,并将下载的数据存放在上述申请的动态内存中。

  • 最后,关闭上述打开的句柄,进行清理工作。

这样,就可以成功实现FTP文件下载的功能了。与服务器建立FTP连接后,我们使用WinInet库中FTP函数对服务器上文件的操作就如果使用Win32 API函数对本地文件操作一样方便。

编码实现

导入WinInet库文件

  1. #include <WinInet.h>
  2. #pragma comment(lib, "WinInet.lib")

FTP文件下载

  1. // 数据下载
  2. // 输入:下载数据的URL路径
  3. // 输出:下载数据内容、下载数据内容长度
  4. BOOL FTPDownload(char *pszDownloadUrl, BYTE **ppDownloadData, DWORD *pdwDownloadDataSize)
  5. {
  6. // INTERNET_SCHEME_HTTPS、INTERNET_SCHEME_HTTP、INTERNET_SCHEME_FTP等
  7. char szScheme[MAX_PATH] = { 0 };
  8. char szHostName[MAX_PATH] = { 0 };
  9. char szUserName[MAX_PATH] = { 0 };
  10. char szPassword[MAX_PATH] = { 0 };
  11. char szUrlPath[MAX_PATH] = { 0 };
  12. char szExtraInfo[MAX_PATH] = { 0 };
  13. ::RtlZeroMemory(szScheme, MAX_PATH);
  14. ::RtlZeroMemory(szHostName, MAX_PATH);
  15. ::RtlZeroMemory(szUserName, MAX_PATH);
  16. ::RtlZeroMemory(szPassword, MAX_PATH);
  17. ::RtlZeroMemory(szUrlPath, MAX_PATH);
  18. ::RtlZeroMemory(szExtraInfo, MAX_PATH);
  19. // 分解URL
  20. if (FALSE == Ftp_UrlCrack(pszDownloadUrl, szScheme, szHostName, szUserName, szPassword, szUrlPath, szExtraInfo, MAX_PATH))
  21. {
  22. return FALSE;
  23. }
  24. if (0 < ::lstrlen(szExtraInfo))
  25. {
  26. // 注意此处的连接
  27. ::lstrcat(szUrlPath, szExtraInfo);
  28. }
  29. HINTERNET hInternet = NULL;
  30. HINTERNET hConnect = NULL;
  31. HINTERNET hFTPFile = NULL;
  32. BYTE *pDownloadData = NULL;
  33. DWORD dwDownloadDataSize = 0;
  34. DWORD dwBufferSize = 4096;
  35. BYTE *pBuf = NULL;
  36. DWORD dwBytesReturn = 0;
  37. DWORD dwOffset = 0;
  38. BOOL bRet = FALSE;
  39. do
  40. {
  41. // 建立会话
  42. hInternet = ::InternetOpen("WinInet Ftp Download V1.0", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
  43. if (NULL == hInternet)
  44. {
  45. Ftp_ShowError("InternetOpen");
  46. break;
  47. }
  48. // 建立连接
  49. hConnect = ::InternetConnect(hInternet, szHostName, INTERNET_INVALID_PORT_NUMBER, szUserName, szPassword, INTERNET_SERVICE_FTP, INTERNET_FLAG_PASSIVE, 0);
  50. if (NULL == hConnect)
  51. {
  52. Ftp_ShowError("InternetConnect");
  53. break;
  54. }
  55. // 打开FTP文件, 文件操作和本地操作相似
  56. hFTPFile = ::FtpOpenFile(hConnect, szUrlPath, GENERIC_READ, FTP_TRANSFER_TYPE_BINARY | INTERNET_FLAG_RELOAD, NULL);
  57. if (NULL == hFTPFile)
  58. {
  59. Ftp_ShowError("FtpOpenFile");
  60. break;;
  61. }
  62. // 获取文件大小
  63. dwDownloadDataSize = ::FtpGetFileSize(hFTPFile, NULL);
  64. // 申请动态内存
  65. pDownloadData = new BYTE[dwDownloadDataSize];
  66. if (NULL == pDownloadData)
  67. {
  68. break;
  69. }
  70. ::RtlZeroMemory(pDownloadData, dwDownloadDataSize);
  71. pBuf = new BYTE[dwBufferSize];
  72. if (NULL == pBuf)
  73. {
  74. break;
  75. }
  76. ::RtlZeroMemory(pBuf, dwBufferSize);
  77. // 接收数据
  78. do
  79. {
  80. bRet = ::InternetReadFile(hFTPFile, pBuf, dwBufferSize, &dwBytesReturn);
  81. if (FALSE == bRet)
  82. {
  83. Ftp_ShowError("InternetReadFile");
  84. break;
  85. }
  86. ::RtlCopyMemory((pDownloadData + dwOffset), pBuf, dwBytesReturn);
  87. dwOffset = dwOffset + dwBytesReturn;
  88. } while (dwDownloadDataSize > dwOffset);
  89. } while (FALSE);
  90. // 返回数据
  91. if (FALSE == bRet)
  92. {
  93. delete[]pDownloadData;
  94. pDownloadData = NULL;
  95. dwDownloadDataSize = 0;
  96. }
  97. *ppDownloadData = pDownloadData;
  98. *pdwDownloadDataSize = dwDownloadDataSize;
  99. // 释放内存并关闭句柄
  100. if (NULL != pBuf)
  101. {
  102. delete []pBuf;
  103. pBuf = NULL;
  104. }
  105. if (NULL != hFTPFile)
  106. {
  107. ::InternetCloseHandle(hFTPFile);
  108. }
  109. if (NULL != hConnect)
  110. {
  111. ::InternetCloseHandle(hConnect);
  112. }
  113. if (NULL != hInternet)
  114. {
  115. ::InternetCloseHandle(hInternet);
  116. }
  117. return bRet;
  118. }

程序测试

在 main 函数中调用上述封装好的函数,进行测试。main 函数为:

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. BYTE *pDownloadData = NULL;
  4. DWORD dwDownloadDataSize = 0;
  5. // 下载
  6. if (FALSE == FTPDownload("ftp://admin:123456789@192.168.0.1/Flower520.zip", &pDownloadData, &dwDownloadDataSize))
  7. {
  8. printf("FTP Download Error!\n");
  9. }
  10. // 将数据保存为文件
  11. Ftp_SaveToFile("myftpdownloadtest.zip", pDownloadData, dwDownloadDataSize);
  12. // 释放内存
  13. delete []pDownloadData;
  14. pDownloadData = NULL;
  15. printf("FTP Download OK.\n");
  16. system("pause");
  17. return 0;
  18. }

测试结果

运行程序,程序提示下载成功。然后,打开目下查看下载文件,成功下载文件。

总结

在打开Internet会话并和服务器建立连接后,接下来使用WinInet库中的FTP函数对服务器上的文件操作,就如同在自己的计算机上使用Win32 API函数操作一样。都是打开或者创建文件,获取文件句柄,然后根据文件句柄,调用函数对文件进行读写操作,最后,关闭文件句柄。

所以,大家注意和本地的文件操作进行类比下,就很容易理解了。

参考

参考自《Windows黑客编程技术详解》一书

上传的附件 cloud_download FTP_Download_Test.7z ( 147.19kb, 4次下载 )

发送私信

上辈子一千次的卖萌,终于换来你今生一次的回眸

17
文章数
21
评论数
最近文章
eject