Windows 下载功能的实现

发布于 2024-12-04 12:31:20 字数 19583 浏览 8 评论 0

笔者计划开发一个自用的包管理工具,需要支持下载功能,笔者尝试了多种 Windows 下载 API,这里分享出来。

URLDownloadToFile

自 Internet Explorer 3.0 开始,Urlmon.dll 中开始提供 URLDownloadToFile,支持从远程服务器上下载文件到本地。 URLDownloadToFile 会先将文件下载到 IE 缓存目录,然后再复制到设置的输出目录,如果第二次下载,就省去了下载时间。 Urlmon 还提供了下载到缓存目录的函数 URLDownloadToCacheFile,正因为 URLDownloadToFile 先下载到缓存目录, 就会出现缓存问题,可以使用 Wininet 中的 DeleteUrlCacheEntry 删除缓存。

使用 URLDownloadToFile 下载文件,下面有个简单的例子:

#include "stdafx.h"
#include <string>
#include <Urlmon.h>
#include <functional>
#pragma comment(lib,"Urlmon")


class DownloadStatus :public IBindStatusCallback {
public:
  ULONG presize{ 0 };
  STDMETHOD(OnStartBinding)(
    /* [in] */ DWORD dwReserved,
    /* [in] */ IBinding __RPC_FAR *pib)
  {
    return E_NOTIMPL;
  }

  STDMETHOD(GetPriority)(
    /* [out] */ LONG __RPC_FAR *pnPriority)
  {
    return E_NOTIMPL;
  }

  STDMETHOD(OnLowResource)(
    /* [in] */ DWORD reserved)
  {
    return E_NOTIMPL;
  }

  STDMETHOD(OnProgress)(
    /* [in] */ ULONG ulProgress,
    /* [in] */ ULONG ulProgressMax,
    /* [in] */ ULONG ulStatusCode,
    /* [in] */ LPCWSTR wszStatusText);

  STDMETHOD(OnStopBinding)(
    /* [in] */ HRESULT hresult,
    /* [unique][in] */ LPCWSTR szError)
  {
    return E_NOTIMPL;
  }

  STDMETHOD(GetBindInfo)(
    /* [out] */ DWORD __RPC_FAR *grfBINDF,
    /* [unique][out][in] */ BINDINFO __RPC_FAR *pbindinfo)
  {
    return E_NOTIMPL;
  }

  STDMETHOD(OnDataAvailable)(
    /* [in] */ DWORD grfBSCF,
    /* [in] */ DWORD dwSize,
    /* [in] */ FORMATETC __RPC_FAR *pformatetc,
    /* [in] */ STGMEDIUM __RPC_FAR *pstgmed)
  {
    return E_NOTIMPL;
  }

  STDMETHOD(OnObjectAvailable)(
    /* [in] */ REFIID riid,
    /* [iid_is][in] */ IUnknown __RPC_FAR *punk)
  {
    return E_NOTIMPL;
  }

  // IUnknown methods.  Note that IE never calls any of these methods, since
  // the caller owns the IBindStatusCallback interface, so the methods all
  // return zero/E_NOTIMPL.

  STDMETHOD_(ULONG, AddRef)()
  {
    return 0;
  }

  STDMETHOD_(ULONG, Release)()
  {
    return 0;
  }

  STDMETHOD(QueryInterface)(
    /* [in] */ REFIID riid,
    /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject)
  {
    return E_NOTIMPL;
  }
};

HRESULT DownloadStatus::OnProgress(ULONG ulProgress, ULONG ulProgressMax,
  ULONG ulStatusCode, LPCWSTR wszStatusText)
{
  float ps = 0;
  if (ulProgressMax != 0) {
    ps = (float)ulProgress * 100 / ulProgressMax;
  }

  fprintf(stderr, "\rDownload %2.2f%%", ps);
  return S_OK;
}

bool DownloadFileWarp(const std::wstring &remoteFile, const std::wstring &localFile) {
  DownloadStatus ds;
  return URLDownloadToFileW(nullptr, remoteFile.c_str(), localFile.c_str(), 0, &ds) == S_OK;
}

在 ReactOS 中,URLDownloadToFile 是使用 WinINet 实现

HttpClient

自 Windows 8 开始,微软推出了 Windows Runtime,Windows Runtime 基于 COM 实现,可以使用 C++/CX,C#,VB.Net,JavaScript 等, 拥有存储,网络,设备,UI,媒体等等。但是如果要使用现代的标准 C++,还是有一些麻烦。

著名 MSDN 专栏作家,Microsoft MVP,Kenny Kerr 近一两年致力于 WinRT 对现代 C++ 的支持,先推出了 Modern C++ for the Windows Runtime ,最近又以微软官方的名义推出了 cppwinrt . 这些项目都是致力于现代 C++ 使用 Windows Runtime API。

在下载文件的时候,我们可以使用 HttpClient 下载文件,下面是一个简单的实例:

#include "stdafx.h"
#include <Shlwapi.h>
#include <winrt/base.h>
#include <winrt/Windows.Web.h>
#include <winrt/Windows.Web.Http.h>
#include <winrt/Windows.Web.Http.Filters.h>
#include <winrt/Windows.Storage.Streams.h>

#pragma comment(lib, "windowsapp")
#pragma comment(lib,"Shlwapi")

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Http;
using namespace Windows::Web::Http::Filters;
using namespace Windows::Storage;
using namespace Windows::Storage::Streams;


// https://msdn.microsoft.com/en-us/library/windows/apps/xaml/windows.web.http.httpclient.aspx 


IAsyncAction DownloadFileInternal(hstring_ref remoteFile,hstring_ref localFile) {
  Uri uri(remoteFile);
  HttpBaseProtocolFilter baseFilter;
  baseFilter.AllowAutoRedirect(true);
  HttpClient client(baseFilter);
  StorageFolder folder=KnownFolders::DocumentsLibrary();
  {
    auto file = co_await folder.CreateFileAsync(localFile);
    auto stream = co_await file.OpenAsync(FileAccessMode::ReadWrite);
    auto result = co_await client.GetAsync(uri);
    auto content = co_await result.Content().WriteToStreamAsync(stream);
  }
}

class RoInitializeWarp {
public:
  RoInitializeWarp() {
    initialize();
  }
  ~RoInitializeWarp() {
    uninitialize();
  }
};

bool WinRTDownloadFile(const std::wstring &remoteFile, const std::wstring &localFile) {
  initialize();
  try {
    DownloadFileInternal(remoteFile.c_str(), localFile.c_str()).get();
  }
  catch (hresult_error const  &e) {
    printf("Error: %ls\n", e.message().c_str());
    uninitialize();
    return false;
  }
  catch (...) {
    return false;
  }
  uninitialize();
  return true;
}

完全使用 cppwinrt 还是有一些问题,比如,StorageFolder 的目录权限问题,然后就是重定向,如果要解决重定向, 还要添加一些复杂的代码,比如 npm.taobao.org 使用 HTTPS 下载重定向 HTTP 就会触发异常,然后异常捕获还是有点麻烦。

当然我是非常期待 cppwinrt 的进一步改进。

Kenny Kerr 在 MSDN 上发布了很多优秀的文章, Windows 平台上原生程序开发人员可以去查看此链接。

Background Intelligent Transfer Service

BITS - Background Intelligent Transfer Service (后台智能传输服务) 是 Windows 的一个重要功能。

笔者在开发 Clangbuilder 时,需要使用 PowerShell 下载软件使用的 cmdlet 是 Start-BitsTransfer, Start-BitsTransfer 实际上使用的是 Windows 的 BITS 服务,而 Windows 更新功能也是使用的 BITS, Chrome 同步扩展也是使用的 BITS.

IBackgroundCopyJob

WinHTTP

WinHTTP 实现下载功能还算比较简单,通常就是发送 HTTP GET 请求,然后创建一个空文件,从 HTTP 响应中读取返回包体, 写入到文件中,直至读取完毕。如果要下载超过 4GB 的文件,那么在获取 HTTP 头部 Content-Length 时需要获取字符串,而不是整数。

#include "stdafx.h"
#include <Shlwapi.h>
#include <windows.h>
#include <winhttp.h>
#include <string>
#include <algorithm>
#include <unordered_map>
#include "console.h"

#pragma comment(lib,"WinHTTP")

#define MinWarp(a,b) ((b<a)?b:a)


struct RequestURL {
  int nPort;
  int nScheme;
  std::wstring scheme;
  std::wstring host;
  std::wstring path;
  std::wstring username;
  std::wstring password;
  std::wstring extrainfo;
  bool Parse(const std::wstring &url) {
    URL_COMPONENTS urlComp;
    DWORD dwUrlLen = 0;
    ZeroMemory(&urlComp, sizeof(urlComp));
    urlComp.dwStructSize = sizeof(urlComp);
    urlComp.dwSchemeLength = (DWORD)-1;
    urlComp.dwHostNameLength = (DWORD)-1;
    urlComp.dwUrlPathLength = (DWORD)-1;
    urlComp.dwExtraInfoLength = (DWORD)-1;

    if (!WinHttpCrackUrl(url.data(), dwUrlLen, 0, &urlComp)) {
      return false;
    }
    scheme.assign(urlComp.lpszScheme, urlComp.dwSchemeLength);
    host.assign(urlComp.lpszHostName, urlComp.dwHostNameLength);
    path.assign(urlComp.lpszUrlPath, urlComp.dwUrlPathLength);
    nPort = urlComp.nPort;
    nScheme = urlComp.nScheme;
    if (urlComp.lpszUserName) {
      username.assign(urlComp.lpszUserName, urlComp.dwUserNameLength);
    }
    if (urlComp.lpszPassword) {
      password.assign(urlComp.lpszPassword, urlComp.dwPasswordLength);
    }
    if (urlComp.lpszExtraInfo) {
      extrainfo.assign(urlComp.lpszExtraInfo,
        urlComp.dwExtraInfoLength);
    }
    return true;
  }
};


#define DEFAULT_USERAGENT L"WindowsGet/1.0"

class InternetObject {
public:
  InternetObject(HINTERNET hInternet):hInternet_(hInternet) {
  }
  operator HINTERNET() {
    return hInternet_;
  }
  operator bool() {
    return hInternet_ != nullptr;
  }
  InternetObject() {
    if (hInternet_) {
      WinHttpCloseHandle(hInternet_);
    }
  }
private:
  HINTERNET hInternet_;
};

bool DownloadFileUseWinHTTP(const std::wstring &url, const std::wstring &localFile,ProgressCallback *callback) {
  RequestURL zurl;
  if (!zurl.Parse(url)) {
    BaseErrorMessagePrint(L"Wrong URL: %s\n",url.c_str());
    return false;
  }
  InternetObject hInternet =
    WinHttpOpen(DEFAULT_USERAGENT, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
      WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
  if (!hInternet) {
    ErrorMessage err(GetLastError());
    BaseErrorMessagePrint(L"WinHttpOpen(): %s", err.message());
    return false;
  }
  //  WinHttpSetOption(hInternet, WINHTTP_OPTION_REDIRECT_POLICY, &dwOption,sizeof(DWORD));
  /// https://msdn.microsoft.com/en-us/library/windows/desktop/aa384066(v=vs.85).aspx 
  /// WINHTTP_PROTOCOL_FLAG_HTTP2
  DWORD dwOption = WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS;
  if (!WinHttpSetOption(hInternet, WINHTTP_OPTION_REDIRECT_POLICY,
    &dwOption, sizeof(DWORD))) {
    ErrorMessage err(GetLastError());
    BaseErrorMessagePrint(L"WINHTTP_OPTION_REDIRECT_POLICY: %s", err.message());
  }
  dwOption = WINHTTP_PROTOCOL_FLAG_HTTP2;
  if (!WinHttpSetOption(hInternet, WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL, &dwOption, sizeof(dwOption))) {
    ErrorMessage err(GetLastError());
    BaseErrorMessagePrint(L"WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL: %s", err.message());
  }
  InternetObject hConnect = WinHttpConnect(hInternet, zurl.host.c_str(),
    (INTERNET_PORT)zurl.nPort, 0);
  if (!hConnect) {
    BaseErrorMessagePrint(L"Server unable to connect: %s", zurl.host.c_str());
    return false;
  }
  DWORD dwOpenRequestFlag =
    (zurl.nScheme == INTERNET_SCHEME_HTTPS) ? WINHTTP_FLAG_SECURE : 0;
  InternetObject hRequest = WinHttpOpenRequest(
    hConnect, L"GET", zurl.path.c_str(), nullptr, WINHTTP_NO_REFERER,
    WINHTTP_DEFAULT_ACCEPT_TYPES, dwOpenRequestFlag);
  if (!hRequest) {
    ErrorMessage err(GetLastError());
    BaseErrorMessagePrint(L"Open Request failed: %s", err.message());
    return false;
  }
  if (WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
    WINHTTP_NO_REQUEST_DATA, 0, 0, 0) == FALSE) {
    ErrorMessage err(GetLastError());
    BaseErrorMessagePrint(L"Send Request failed: %s", err.message());
    return false;
  }
  if (WinHttpReceiveResponse(hRequest, NULL) == FALSE) {
    ErrorMessage err(GetLastError());
    BaseErrorMessagePrint(L"Receive Response failed: %s", err.message());
    return false;
  }
  DWORD dwStatusCode = 0;
  DWORD dwXsize = sizeof(dwStatusCode);
  if (!WinHttpQueryHeaders(hRequest,
    WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
    WINHTTP_HEADER_NAME_BY_INDEX, &dwStatusCode, &dwXsize,
    WINHTTP_NO_HEADER_INDEX)) {
    ErrorMessage err(GetLastError());
    BaseErrorMessagePrint(L"WinHttpQueryHeaders failed: %s", err.message());
    return false;
  }
  if (dwStatusCode != 200 && dwStatusCode != 201) {
    BaseErrorMessagePrint(L"Reponse status code: %d\n",dwStatusCode);
    return false;
  }
  uint64_t dwContentLength=0;
  wchar_t conlen[36];
  dwXsize = sizeof(conlen);
  if (WinHttpQueryHeaders(hRequest,
    WINHTTP_QUERY_CONTENT_LENGTH,
    WINHTTP_HEADER_NAME_BY_INDEX,conlen, &dwXsize,
    WINHTTP_NO_HEADER_INDEX)) {
  }
  wchar_t *cx = nullptr;
  dwContentLength=wcstoull(conlen, &cx, 10);
  wprintf(L"File size: %lld\n", dwContentLength);
  std::wstring tmp = localFile + L".part";
  HANDLE hFile =
    CreateFileW(tmp.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
      NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  ///
  uint64_t total = 0;
  DWORD dwSize = 0;
  char fixedsizebuf[16384];
  ///
  if (callback) {
    callback->impl(0, callback->userdata);
  }
  do {
    // Check for available data.
    if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
      break;
    }
    total += dwSize;
    if (dwContentLength > 0) {
      if (callback) {
        callback->impl(total * 100 / dwContentLength, callback->userdata);
      }
    }
    auto dwSizeN = dwSize;
    while (dwSizeN > 0) {
      DWORD dwDownloaded = 0;
      if (!WinHttpReadData(hRequest, (LPVOID)fixedsizebuf, MinWarp(sizeof(fixedsizebuf), dwSizeN), &dwDownloaded)) {
        break;
      }
      dwSizeN = dwSizeN - dwDownloaded;
      DWORD wmWritten;
      WriteFile(hFile, fixedsizebuf, dwSize, &wmWritten, NULL);
    }
  } while (dwSize > 0);
  if (callback) {
    callback->impl(100, callback->userdata);
  }
  std::wstring npath = localFile;
  int i = 1;
  while (PathFileExistsW(npath.c_str())) {
    auto n = localFile.find_last_of('.');
    if (n != std::wstring::npos) {
      npath = localFile.substr(0, n) + L"(";
      npath += std::to_wstring(i);
      npath += L")";
      npath += localFile.substr(n);
    }
    else {
      npath = localFile + L"(" + std::to_wstring(i) + L")";
    }
    i++;
  }
  CloseHandle(hFile);
  MoveFileExW(tmp.c_str(), npath.c_str(), MOVEFILE_COPY_ALLOWED);
  return true;
}

HTTP2 支持

自 Windows 10 1607 起,WinHTTP 允许开发者通过 WinHttpSetOption 开启 HTTP2 支持。 上述源码中就有关于 HTTP2.0 的设置。

Wininet

Wininet 流程和 WinHTTP 类似,下面是实现代码:

#include "stdafx.h"
#include <Windows.h>
#include <WinInet.h>
#include <Shlwapi.h>
#include "console.h"

#pragma comment(lib, "WinInet.lib")
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa385328(v=vs.85).aspx 
//INTERNET_OPTION_ENABLE_HTTP_PROTOCOL
//HTTP_PROTOCOL_FLAG_HTTP2
struct WinINetRequestURL {
  int nPort=0;
  int nScheme=0;
  std::wstring scheme;
  std::wstring host;
  std::wstring path;
  std::wstring username;
  std::wstring password;
  std::wstring extrainfo;
  bool Parse(const std::wstring &url) {
    URL_COMPONENTS urlComp;
    DWORD dwUrlLen = 0;
    ZeroMemory(&urlComp, sizeof(urlComp));
    urlComp.dwStructSize = sizeof(urlComp);
    urlComp.dwSchemeLength = (DWORD)-1;
    urlComp.dwHostNameLength = (DWORD)-1;
    urlComp.dwUrlPathLength = (DWORD)-1;
    urlComp.dwExtraInfoLength = (DWORD)-1;

    if (!InternetCrackUrlW(url.data(), dwUrlLen, 0, &urlComp)) {
      return false;
    }
    scheme.assign(urlComp.lpszScheme, urlComp.dwSchemeLength);
    host.assign(urlComp.lpszHostName, urlComp.dwHostNameLength);
    path.assign(urlComp.lpszUrlPath, urlComp.dwUrlPathLength);
    nPort = urlComp.nPort;
    nScheme = urlComp.nScheme;
    if (urlComp.lpszUserName) {
      username.assign(urlComp.lpszUserName, urlComp.dwUserNameLength);
    }
    if (urlComp.lpszPassword) {
      password.assign(urlComp.lpszPassword, urlComp.dwPasswordLength);
    }
    if (urlComp.lpszExtraInfo) {
      extrainfo.assign(urlComp.lpszExtraInfo,
        urlComp.dwExtraInfoLength);
    }
    return true;
  }
};


class WinINetObject {
public:
  WinINetObject(HINTERNET hInternet) :hInternet_(hInternet) {
  }
  operator HINTERNET() {
    return hInternet_;
  }
  operator bool() {
    return hInternet_ != nullptr;
  }
  WinINetObject() {
    if (hInternet_) {
      InternetCloseHandle(hInternet_);
    }
  }
private:
  HINTERNET hInternet_;
};

bool DownloadFileUseWininet(const std::wstring &url, const std::wstring &localFile, ProgressCallback *callback) {
  WinINetRequestURL zurl;
  if (!zurl.Parse(url)) {
    BaseErrorMessagePrint(L"Wrong URL: %s\n", url.c_str());
    return false;
  }
  DeleteUrlCacheEntryW(url.c_str());
  WinINetObject hInet = InternetOpenW(L"WindowsGet", INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0);
  if (!hInet) {
    ErrorMessage err(GetLastError());
    BaseErrorMessagePrint(L"InternetOpenW(): %s", err.message());
    return false;
  }
  DWORD  dwOption = HTTP_PROTOCOL_FLAG_HTTP2;
  InternetSetOptionW(hInet, INTERNET_OPTION_ENABLE_HTTP_PROTOCOL,&dwOption,sizeof(dwOption));

  WinINetObject hConnect = InternetConnectW(hInet, zurl.host.c_str(),
    (INTERNET_PORT)zurl.nPort, nullptr, nullptr, INTERNET_SERVICE_HTTP, 0, NULL);
  if (!hConnect) {
    ErrorMessage err(GetLastError());
    BaseErrorMessagePrint(L"InternetConnectW(): %s", err.message());
    return false;
  }
  DWORD dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |
    INTERNET_FLAG_KEEP_CONNECTION |
    INTERNET_FLAG_NO_AUTH |
    INTERNET_FLAG_NO_COOKIES |
    INTERNET_FLAG_NO_UI |
    INTERNET_FLAG_SECURE |
    INTERNET_FLAG_IGNORE_CERT_CN_INVALID |
    INTERNET_FLAG_RELOAD;

  DWORD64 dwContentLength=1;
  WinINetObject hRequest = InternetOpenUrlW(hInet, url.c_str(), nullptr, 0,
    dwOpenRequestFlags, 0);
  if (zurl.nScheme == INTERNET_SCHEME_HTTP
    || zurl.nScheme == INTERNET_SCHEME_HTTPS) {
    DWORD dwStatusCode = 0;
    DWORD dwSizeLength = sizeof(dwStatusCode);
    if (HttpQueryInfoW(hRequest,
      HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE,
      &dwStatusCode, &dwSizeLength, nullptr)) {
      return false;
    }
    wchar_t szbuf[20];
    dwSizeLength = sizeof(szbuf);
    HttpQueryInfoW(hRequest,HTTP_QUERY_CONTENT_LENGTH, 
      szbuf, &dwSizeLength, nullptr);
    wchar_t *cx;
    dwContentLength = wcstoull(szbuf, &cx, 10);
    fprintf(stderr, "Content-Length: %llu\n", dwContentLength);
  }
  else if(zurl.nScheme==URL_SCHEME_FTP) {
    DWORD highSize=0;
    auto loSize=FtpGetFileSize(hRequest, &highSize);
    dwContentLength = ((DWORD64)highSize << 32)+loSize ;
  }
  //InternetQueryDataAvailable
  if (!hRequest) {
    ErrorMessage err(GetLastError());
    BaseErrorMessagePrint(L"InternetOpenUrlW(): %s", err.message());
    return false;
  }

  // lpszVersion ->nullptr ,use config
  std::wstring tmp = localFile + L".part";
  HANDLE hFile =
    CreateFileW(tmp.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
      NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  ///
  BYTE fixedsizebuf[16384];
  //DWORD64 rdsize = 0;
  DWORD dwReadSize = 0;
  DWORD dwWriteSize = 0;
  uint64_t rdsize = 0;
  if (callback) {
    callback->impl(0, callback->userdata);
  }
  BOOL result=true;
  do {
    result=InternetReadFile(hRequest, fixedsizebuf, sizeof(fixedsizebuf), &dwReadSize);
    if (!result) {
      ErrorMessage err(GetLastError());
      BaseErrorMessagePrint(L"HttpOpenRequestW(): %s", err.message());
    }
    WriteFile(hFile, fixedsizebuf, dwReadSize, &dwWriteSize, nullptr);
    rdsize += dwReadSize;
    if (callback) {
      callback->impl(rdsize *100/ dwContentLength, callback->userdata);
    }
  } while (result&&dwReadSize);
  if (callback) {
    callback->impl(100, callback->userdata);
  }
  std::wstring npath = localFile;
  int i = 1;
  while (PathFileExistsW(npath.c_str())) {
    auto n = localFile.find_last_of('.');
    if (n != std::wstring::npos) {
      npath = localFile.substr(0, n) + L"(";
      npath += std::to_wstring(i);
      npath += L")";
      npath += localFile.substr(n);
    }
    else {
      npath = localFile + L"(" + std::to_wstring(i) + L")";
    }
    i++;
  }
  CloseHandle(hFile);
  MoveFileExW(tmp.c_str(), npath.c_str(), MOVEFILE_COPY_ALLOWED);
  return true;
}

HTTP2 支持

自 Windows 10 1507 起, Wininet 便允许开发者通过设置参数开启 HTTP2 支持

如何选择

除了以上的解决方案外,在 Windows 系统中实现下载功能还有很多其他的选择,比如试用 libcurl,Poco Net,cpp-netlib, cpprestsdk,QNetworkRequest 等等。就 HTTP URL 的下载来说,如果不想使用现成的 HTTP 库实现下载,还可以自己实现 HTTP 库,可以使用原生的 socket,也可以使用 Boost.Asio,libuv,libev,ACE 来实现 HTTP 客户端。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

笑忘罢

暂无简介

0 文章
0 评论
25 人气
更多

推荐作者

马化腾

文章 0 评论 0

thousandcents

文章 0 评论 0

辰『辰』

文章 0 评论 0

ailin001

文章 0 评论 0

冷情妓

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文