Windows 下载功能的实现

发布于 2024-12-04 12:31:20 字数 19583 浏览 27 评论 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技术交流群

发布评论

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

关于作者

笑忘罢

暂无简介

文章
评论
28 人气
更多

推荐作者

李珊平

文章 0 评论 0

Quxin

文章 0 评论 0

范无咎

文章 0 评论 0

github_ZOJ2N8YxBm

文章 0 评论 0

若言

文章 0 评论 0

南…巷孤猫

文章 0 评论 0

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