借助C++进行Windows开发:异步WinHTTP2010-04-16 MSDN / Kenny Kerr目录WinHTTP 概述会话对象连接对象请求对象请求通知请求取消发送请求数据其他功能由于分布式编程的发展,大多数基于 Windows® 的现今的应用程序必须能够执行 HTTP 请求。虽然 HTTP 相对简单,但现今的 HTTP 的处理却未必简单。异步处理需要缓冲大量的请求和响应、身份验证、自动代理服务器检测、持久连接等操作。当然,您可以忽略其中的许多问题,但这会影响应用程序的质量,而且模拟 HTTP 不像模拟 TCP 套接字那样简单。那么,C++ 开发人员的任务是什么?一个常见的误解是,如果希望应用程序访问 Web,则需要使用 Microsoft® .NET Framework。事实上,使用托管代码的开发人员仍然必须处理许多我刚刚提到的问题,而且具象状态传输 (REST) 等许多新式 Web 服务使用本机代码进行编程也很容易。在本月的专栏中,我将介绍如何使用 Windows HTTP 服务(又称 WinHTTP)API 实现 HTTP 客户端。此外,在 Windows Vista® 和 Windows Server® 2008 中,WinHTTP 支持上载大于 4GB 的文件、改进了基于证书的身份验证,并且还提供了检索源 IP 地址和目标 IP 地址的功能。WinHTTP 既可提供 C API,又可提供 COM API。最初的 C API 自然较难使用,但是稍微借助一下 C++,它就可以为构建 HTTP 客户端提供功能强大且灵活的 API。此外,它还同时提供同步编程模型和异步编程模型。我将重点介绍异步模型,原因如下:首先,在当今的并发感知领域中,并行编程非常重要。其次,在文档和培训中,通常会过多强调单线程编程,而忽略了对并行编程的介绍。或许有人认为并行编程太简单,不需要专门提供说明性指导。虽然许多开发人员不愿意使用并行编程,但是您很快就会发现它相当自然,甚至很有趣(哦,您对有趣的定义可能与我不同,但不要泄气)!WinHTTP 概述虽然在 C API 中并不明显,但 WinHTTP API 实际是在逻辑上作为三个独立对象进行建模:会话、连接和请求。初始化 WinHTTP 需要会话对象。每个应用程序只需要一个会话。会话有助于创建连接对象。您希望与之进行通信的每个 HTTP 服务器均需要一个连接对象,而连接对象也有助于创建单个请求对象。第一次发送请求后就会建立实际的网络连接。图 1 中描述了这些对象之间的关系。一个会话可能具有多个活动连接,并且一个连接可能包含多个并发请求。

图 1 会话、连接和请求会话对象、连接对象和请求对象可由 HINTERNET 句柄表示。虽然创建这些对象时使用的函数不同,但是如果将各个对象的相应句柄传递给 WinHttpCloseHandle 函数,这些对象都会被损坏。此外,一些函数(如 WinHttpQueryOption 和 WinHttpSetOption)使用句柄查询和设置 WinHTTP 对象支持的各种不同选项。图 2 列出了 WinHttpHandle 类,可将其用于管理 WinHTTP 句柄中所有三种类型的生存期,以及查询和设置这些句柄上的选项。因此,如果给定一个请求对象,您就可以使用 WINHTTP_OPTION_URL 选项检索其完整 URL:
DWORD length = 0;
request.QueryOption(WINHTTP_OPTION_URL, 0, length);
ASSERT(ERROR_INSUFFICIENT_BUFFER == ::GetLastError());
CString url;
COM_VERIFY(request.QueryOption(WINHTTP_OPTION_URL,
url.GetBufferSetLength(length / sizeof(WCHAR)), length));
url.ReleaseBuffer();

图 2 WinHttpHandle 类
class WinHttpHandle
{
public:
WinHttpHandle() :
m_handle(0)
{}
~WinHttpHandle()
{
Close();
}
bool Attach(HINTERNET handle)
{
ASSERT(0 == m_h);
m_handle = handle;
return 0 != m_handle;
}
HINTERNET Detach()
{
HANDLE handle = m_handle;
m_handle = 0;
return handle;
}
void Close()
{
if (0 != m_handle)
{
VERIFY(::WinHttpCloseHandle(m_handle));
m_handle = 0;
}
}
HRESULT SetOption(DWORD option,
const void* value,
DWORD length)
{
if (!::WinHttpSetOption(m_handle,
option,
const_cast<void*>(value),
length))
{
return HRESULT_FROM_WIN32(::GetLastError());
}
return S_OK;
}
HRESULT QueryOption(DWORD option,
void* value,
DWORD& length) const
{
if (!::WinHttpQueryOption(m_handle,
option,
value,
&length))
{
return HRESULT_FROM_WIN32(::GetLastError());
}
return S_OK;
}
HINTERNET m_handle;
};
我在代码段中使用 COM_VERIFY 宏来明确指出函数返回必须要检查的 HRESULT 的位置。您可以使用相应的错误处理代替此操作,不论它是引发异常还是您自行返回 HRESULT 都可以。