1. 项目背景与核心价值
在现代Windows桌面应用开发中,MFC(Microsoft Foundation Classes)依然是许多传统企业级应用的首选框架。而随着业务系统逐渐向云端迁移,MFC程序与Web API的交互需求日益增多。WinHttp作为Windows平台原生的HTTP客户端组件,相比第三方库具有更好的系统兼容性和更低的依赖成本。
我在多个工业控制系统中采用这种方案时发现,合理使用WinHttp可以实现:
- 与现有MFC代码的无缝集成
- 不引入额外运行时依赖
- 支持Windows认证等企业级特性
- 保持与WinINet相当的易用性但更轻量
2. 环境准备与基础配置
2.1 开发环境要求
确保开发环境满足:
- Visual Studio 2015或更高版本
- Windows SDK版本与目标系统匹配
- MFC项目已启用Unicode字符集(重要!)
注意:WinHttp的宽窄字符API行为差异很大,现代Windows开发应始终使用Unicode版本。
2.2 添加必要的头文件和库
在stdafx.h中添加:
cpp复制#include <winhttp.h>
#pragma comment(lib, "winhttp.lib")
对于需要HTTPS支持的情况,建议额外链接加密库:
cpp复制#pragma comment(lib, "crypt32.lib")
3. WinHttp核心流程实现
3.1 初始化会话与连接
典型的三层对象模型使用方式:
cpp复制HINTERNET hSession = WinHttpOpen(
L"MFC Client/1.0",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0);
HINTERNET hConnect = WinHttpConnect(
hSession,
L"api.example.com",
INTERNET_DEFAULT_HTTPS_PORT, 0);
关键参数说明:
- 访问类型建议使用
WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY以支持现代Windows的代理配置 - 端口号根据协议选择:HTTP=80, HTTPS=443
- 超时设置应通过
WinHttpSetTimeouts单独配置
3.2 构造并发送HTTP请求
POST请求示例:
cpp复制HINTERNET hRequest = WinHttpOpenRequest(
hConnect, L"POST", L"/v1/data",
NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
WINHTTP_FLAG_SECURE);
// 设置JSON内容类型
WinHttpAddRequestHeaders(hRequest,
L"Content-Type: application/json\r\n",
-1L, WINHTTP_ADDREQ_FLAG_ADD);
// 准备JSON数据
CStringA jsonData = "{ \"key\": \"value\" }";
WinHttpSendRequest(hRequest,
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
(LPVOID)(LPCSTR)jsonData,
jsonData.GetLength(),
jsonData.GetLength(), 0);
4. 响应处理与错误管理
4.1 接收响应数据
推荐的分块读取方式:
cpp复制CStringA responseData;
DWORD dwSize = 0;
DWORD dwDownloaded = 0;
do {
// 检查数据可用性
if (!WinHttpQueryDataAvailable(hRequest, &dwSize))
break;
// 分配缓冲区
auto pszOutBuffer = new char[dwSize+1];
// 读取数据
if (!WinHttpReadData(hRequest, pszOutBuffer, dwSize, &dwDownloaded)) {
delete[] pszOutBuffer;
break;
}
// 追加到结果
responseData.Append(pszOutBuffer, dwDownloaded);
delete[] pszOutBuffer;
} while (dwSize > 0);
4.2 错误处理最佳实践
建议的错误处理框架:
cpp复制DWORD dwError = GetLastError();
if (dwError != ERROR_SUCCESS) {
CString strError;
strError.Format(L"Error %d: ", dwError);
LPWSTR pBuffer = NULL;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_HMODULE |
FORMAT_MESSAGE_IGNORE_INSERTS,
GetModuleHandle(L"winhttp.dll"),
dwError,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&pBuffer, 0, NULL);
strError += pBuffer;
LocalFree(pBuffer);
AfxMessageBox(strError);
}
5. 高级功能实现
5.1 异步请求处理
创建异步会话:
cpp复制HINTERNET hSession = WinHttpOpen(
L"MFC Async Client",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS,
WINHTTP_FLAG_ASYNC);
// 设置回调函数
WinHttpSetStatusCallback(
hSession,
WinHttpCallback,
WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS,
NULL);
回调函数示例:
cpp复制void CALLBACK WinHttpCallback(
HINTERNET hInternet,
DWORD_PTR dwContext,
DWORD dwInternetStatus,
LPVOID lpvStatusInformation,
DWORD dwStatusInformationLength)
{
switch (dwInternetStatus) {
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
// 触发请求发送完成
break;
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
// 响应头可用
break;
case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
// 数据可读取
break;
}
}
5.2 企业级安全配置
证书验证强化:
cpp复制// 设置安全选项
DWORD dwSecurityFlags =
SECURITY_FLAG_IGNORE_UNKNOWN_CA |
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
SECURITY_FLAG_IGNORE_CERT_CN_INVALID;
WinHttpSetOption(
hRequest,
WINHTTP_OPTION_SECURITY_FLAGS,
&dwSecurityFlags,
sizeof(dwSecurityFlags));
NTLM认证示例:
cpp复制WinHttpSetCredentials(
hRequest,
WINHTTP_AUTH_TARGET_SERVER,
WINHTTP_AUTH_SCHEME_NTLM,
L"username", L"password", NULL);
6. 性能优化技巧
6.1 连接池管理
重用会话对象:
cpp复制class CHttpSession {
public:
CHttpSession() {
m_hSession = WinHttpOpen(...);
}
~CHttpSession() {
if (m_hSession) WinHttpCloseHandle(m_hSession);
}
operator HINTERNET() { return m_hSession; }
private:
HINTERNET m_hSession;
};
// 应用全局保持单个实例
CHttpSession g_httpSession;
6.2 压缩传输支持
启用GZIP压缩:
cpp复制// 请求头添加
WinHttpAddRequestHeaders(hRequest,
L"Accept-Encoding: gzip, deflate\r\n",
-1L, WINHTTP_ADDREQ_FLAG_ADD);
// 响应处理时检查
DWORD dwEncoding = 0;
DWORD dwSize = sizeof(dwEncoding);
WinHttpQueryHeaders(hRequest,
WINHTTP_QUERY_CONTENT_ENCODING | WINHTTP_QUERY_FLAG_NUMBER,
NULL, &dwEncoding, &dwSize, NULL);
7. 常见问题排查
7.1 典型错误代码处理
| 错误代码 | 原因 | 解决方案 |
|---|---|---|
| ERROR_WINHTTP_TIMEOUT | 请求超时 | 检查WinHttpSetTimeouts设置 |
| ERROR_WINHTTP_SECURE_FAILURE | SSL问题 | 验证证书或调整安全标志 |
| ERROR_WINHTTP_NAME_NOT_RESOLVED | DNS解析失败 | 检查域名拼写和网络连接 |
| ERROR_WINHTTP_INVALID_SERVER_RESPONSE | 服务器响应异常 | 检查API端点是否正确 |
7.2 内存泄漏预防
资源释放顺序:
cpp复制if (hRequest) WinHttpCloseHandle(hRequest);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);
关键点:必须按照请求→连接→会话的顺序关闭句柄,逆序操作可能导致内存泄漏。
8. 实际项目中的经验总结
在工业控制系统集成项目中,我总结了以下实战经验:
- 心跳检测机制:对于长连接场景,建议每5分钟发送一次HEAD请求保持连接活跃
cpp复制WinHttpOpenRequest(hConnect, L"HEAD", L"/health", ...);
- 重试策略实现:对临时性错误实现指数退避重试
cpp复制int retries = 0;
do {
if (SendRequest()) break;
Sleep(1000 * (1 << retries));
} while (++retries < 3);
- 调试日志记录:启用WinHttp跟踪日志
cpp复制DWORD dwEnable = WINHTTP_ENABLE_SSL_REVOCATION;
WinHttpSetOption(NULL,
WINHTTP_OPTION_ENABLE_FEATURE,
&dwEnable, sizeof(dwEnable));