Skip to main content
本文介绍如何在 Microsoft 游戏开发工具包 (GDK) 游戏中使用 Windows HTTP Services (WinHTTP) 功能。它是可用于 PC 和 XBOX 主机 Microsoft 游戏开发工具包 (GDK) 游戏的较底层 HTTP 客户端 API。你可以使用它创建常规 HTTP 与 WebSocket 服务端点。 由于它较为底层,实现时需要考虑更多因素,也需要更多步骤才能实现安全且健壮的通信。我们建议你的游戏实现遵循所有 通信安全最佳实践 (NDA 文章)

WinHTTP 版本差异

总的来说,Microsoft 游戏开发工具包 (GDK) 游戏与 WinHTTP 的交互方式与 Win32 应用程序与 WinHTTP 的交互方式相同。 在为 Microsoft 游戏开发工具包 (GDK) 游戏开发时,只有 WinHTTP 的扁平 C/C++ API 可用。这意味着 HTTP 功能必须基于此 HTTP 客户端 API 构建。

将 WinHTTP 添加到 XBOX 主机项目

在主机端,源文件中应 #include <winhttp.h>。必须链接 XGamePlatform.lib,而不是直接链接 Winhttp.lib。在 Microsoft 游戏开发工具包 (GDK) 游戏中,只有 WINAPI_PARTITION_GAMES API 系列下的 API 可用。在 Windows PC 上继续链接 Winhttp.lib 有关如何将 WinHTTP 集成到 Microsoft 游戏开发工具包 (GDK) 游戏的示例,请参阅 SimpleWinHttp 示例。它为你自己的 WinHTTP 实现提供了一个良好的起点,并包含 WinHttpManager 类,暴露了简单的异步 API 面。

网络初始化与 WinHTTP

在你的游戏首次调用 WinHttpOpen 之前,Microsoft 游戏开发工具包 (GDK) 游戏必须确保网络堆栈已初始化。如果在游戏启动过程中过早调用 WinHttpOpen,那么 WinHttpOpen 或后续 WinHTTP 调用可能以不确定的方式失败或崩溃。请求可能表面上成功但实际上失败,反之亦然,直到网络被声明为已初始化为止。有关如何判断网络堆栈已初始化的详细信息,请参阅 网络初始化

游戏挂起/恢复与 WinHTTP

收到游戏挂起通知时,游戏应启动关闭所有 WinHTTP 句柄的过程。WinHTTP 句柄清理是异步的。因此,应按以下顺序关闭句柄:先关闭所有请求句柄,然后关闭所有连接句柄,最后关闭所有会话句柄。WinHTTP 句柄清理之所以是异步的,是为了确保通知线程的安全。虽然是异步的,但 WinHTTP 句柄清理不会延迟任何时间,可以轻松放入一秒的挂起延迟超时内。 在恢复时,游戏应按前述“网络初始化与 WinHTTP”节所述的步骤操作,等待网络回到就绪状态后再继续使用 WinHTTP。挂起与恢复事件之间可能间隔很长时间,网络需要重新稳定后 WinHTTP API 才能再次表现出确定性。

内存与并发注意事项

并发 WinHTTP 请求的数量应始终保持在 8 个以下,以确保 WinHTTP 内部的异步状态能正常工作并保持在其内存预算之内。此限制适用于游戏运行时中所有并发操作,包括来自 XBOX 服务 API 和 XCurl 的调用。 作为 WinSock 内存注意事项 的补充,接收数据时应始终保证有一个通过 WinHttpReadData 挂起的缓冲区(或正在等待 WinHttpQueryDataAvailable 调用的回调),以尽快将数据从内核态内存池转移到你的用户态进程中,最大限度地减少 HTTP 操作所消耗的内核内存。 WinHttpQueryHeaders 的 getter 函数需要临时的内存分配。它会在内部分配大小等于 lpdwBufferLength 参数的临时缓冲区(并在函数返回前释放)。因此,你应使用 WINHTTP_NO_OUTPUT_BUFFER 双次调用模式来尽量减小临时缓冲区大小,并限制同时进行的 WinHttpQueryHeaders 调用数量,以避免占用过多系统内存导致系统不稳定。头部默认最大尺寸为 64 KB,由 WINHTTP_OPTION_MAX_RESPONSE_HEADER_SIZE WinHTTP 选项指定。

WinHttpOpen 注意事项

标志

必须为 WinHttpOpen 传入下表中的标志。 WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXYWINHTTP_NO_PROXY_NAMEWINHTTP_NO_PROXY_BYPASS 的组合允许 Microsoft 游戏开发工具包 (GDK) 平台自动处理诸如 Fiddler 以及其他边缘网络环境下的代理。 WINHTTP_FLAG_SECURE_DEFAULTS 标志是一个新标志,旨在通过设置推荐的安全连接行为来帮助 Microsoft 游戏开发工具包 (GDK) 游戏遵循安全最佳实践。它在 XBOX One 主机上可用,未来 Windows OS 更新中将可用于 Windows PC。在不支持该标志的现有 Windows OS 版本上传入 WINHTTP_FLAG_SECURE_DEFAULTS 会导致无效参数失败。此标志有一个显著副作用——它会强制 WinHTTP 进入异步模式,因为该标志隐式包含了 WINHTTP_FLAG_ASYNC 标志。在不支持此标志的 Windows PC OS 版本上,应改为传入 WINHTTP_FLAG_ASYNC,以尽量减小其余 WinHTTP 实现上的差异。
WINHTTP_FLAG_SECURE_DEFAULTS 标志要求在 WinHttpOpenRequest 中传入匹配的 WINHTTP_FLAG_SECURE 标志,并会阻止未加密的 HTTP 请求。在开发套件上进行内部调试与测试时,可以创建一个 WinHTTP 会话句柄并向 WinHttpOpen 指定 WINHTTP_FLAG_ASYNC 标志。此标志允许你在开发期间发起未加密的 HTTP 请求,只需在 WinHttpOpenRequest 中不指定 WINHTTP_FLAG_SECURE 标志即可。对于非调试流量,仍应使用以 WINHTTP_FLAG_SECURE_DEFAULTS 打开的会话句柄,以匹配游戏在 RETAIL 中看到的请求行为。

WINHTTP_OPTION_SECURE_PROTOCOLS

在使用 WinHttpOpen 创建新的会话句柄之后,必须调用 WinHttpSetOption,选项为 WINHTTP_OPTION_SECURE_PROTOCOLS,并传入与将要在此会话句柄上使用的匹配 URL 相对应、通过调用 XNetworkingQuerySecurityInformationForUrlUtf16Async 获取的 XNetworkingSecurityInformation::enabledHttpSecurityProtocolFlags。你还应将 XNetworkingSecurityInformation 结构存储在你的上下文对象中,以便稍后在验证 TLS/SSL 握手时使用。

缓存会话句柄

通过 WinHttpOpen 创建的 HTTP 会话句柄从内存角度看代价较高,并且会带来较大的启动开销,从而延迟第一个 HTTP 请求。我们建议你在游戏中尽可能地缓存 HTTP 会话句柄以避免这些开销。 但是,无法在已有会话句柄上更改 WINHTTP_OPTION_SECURE_PROTOCOLS 选项。你应保留一个 XNetworkingSecurityInformation::enabledHttpSecurityProtocolFlags 值到 WinHTTP 会话句柄的缓存映射,以确保每种不同的安全协议标志都对应不同的会话句柄。 游戏维护的缓存必须在收到挂起通知时清除,并应在恢复时(等待网络初始化完成后)从头重建。

WinHttpConnect 注意事项

与会话句柄不同,通过 WinHttpConnect 创建的连接句柄绝不应缓存。每个新请求和/或重试尝试都应创建新的句柄。WinHTTP 连接句柄尽管名为连接,却与底层服务器的 TCP (传输控制协议) 连接没有关系。WinHTTP 通过会话句柄管理底层服务器连接的生命周期,并会尽可能自动地将已打开的服务器连接复用给新的连接句柄。

URL 规范化

WinHTTP 要求所有 URL 都规范化为 a-z、A-Z 和 0-9 的 US-ASCII 字符。有关规范化的更多信息,请参阅 WinHTTP 中的 URL。可能的情况下,我们建议将游戏使用的 URL 以规范化形式硬编码。这种形式可避免使用 WinHttpCrackUrlWinHttpCreateUrl 动态规范化 URL 所带来的内存分配和性能问题。

URL 拆分

WinHTTP 要求向 WinHttpConnect 传入以 null 结尾的主机名字符串,而路径和对象则传给 WinHttpOpenRequest。你的游戏在某些地方必须传入拼接后的主机名与路径的完整 URL,而在另一些地方只需传主机名或路径。我们建议你在游戏中硬编码这两者,避免使用 WinHttpCrackUrlWinHttpCreateUrl 动态拼接或拆分 URL。

WinHttpOpenRequest 注意事项

与 WinHTTP 连接句柄类似,通过 WinHttpOpenRequest 函数创建的 WinHTTP 请求句柄绝不应缓存。每个新请求和/或重试尝试都应创建新的句柄。 作为安全最佳实践,在调用 WinHttpOpenRequest 函数时游戏应始终为 dwFlags 参数传入 WINHTTP_FLAG_SECURE 标志。

检索并应用 XBOX 服务令牌

Microsoft 游戏开发工具包 (GDK) 游戏不会自动插入令牌。相反,游戏应使用 Microsoft 游戏开发工具包 (GDK) 的 XUser API 检索 XBOX 服务身份验证令牌与签名。当游戏拥有用户后,应对每个请求分别调用 XUserGetTokenAndSignatureUtf16Async 来获取令牌与签名字符串。然后应将这两个字符串作为标头传给对 WinHttpAddRequestHeadersExWinHttpSendRequestWinHttpAddRequestHeaders 的调用。 为生成正确的签名,XUserGetTokenAndSignatureUtf16Async 需要游戏传入所有标头以及整个正文。对于带有大正文的 POSTPUT,游戏可传入在 Partner Center 中配置的正文子集。有关更多信息,请参阅 Web 服务 (NDA 主题)。目前 XBOX 网络未提供检索此配置的机制,客户端应硬编码这些值或通过自定义的游戏特定端点获取它们。 XUserGetTokenAndSignatureUtf16Async 在内部执行所有必要的缓存,对每次 HTTP 尝试(包括重试)都应调用一次。如果游戏在任何 HTTP 请求上收到 401 Unauthorized HTTP 响应状态码,游戏应重试请求并强制刷新 XBOX 服务身份验证令牌。方法是通过 XUserGetTokenAndSignatureUtf16Async 获取新令牌,并传入 XUserGetTokenAndSignatureOptions::ForceRefresh 枚举值。 当游戏拿到通过 XUserGetTokenAndSignatureUtf16Async 获取的 XUserGetTokenAndSignatureUtf16Data 后,游戏必须将 XUserGetTokenAndSignatureUtf16Data::TokenXUserGetTokenAndSignatureUtf16Data::Signature 转换为 HTTP 标头传给 WinHTTP。为降低 Microsoft 游戏开发工具包 (GDK) 游戏的复杂度,专门新增了一个 WinHTTP API WinHttpAddRequestHeadersEx。下面展示了如何使用此新 API 的示例。该新 API 在 XBOX One 主机上可用,未来 Windows OS 更新中将可用于 Windows PC。在主机上,我们建议使用 WinHttpAddRequestHeadersEx 以避免额外的分配和字符串格式变更。
设备或已登录账户需要有权访问其所设定的沙盒。否则,XUserGetTokenAndSignatureUtf16Data 会失败。

使用 XBOX Network Security Authorization List (NSAL)

XBOX 网络使用 NSAL 来确保客户端与你的 Web 服务之间建立安全且经过身份认证的连接。游戏在 Partner Center 中作为其配置的一部分管理 NSAL 的内容。有关更多信息,请参阅 在 Partner Center 中设置 Web 服务 (NDA 主题)。随后每个游戏都会自动下载 NSAL 配置。它既用于生成合适的 XBOX 服务令牌,也用于为游戏的特定端点执行证书固定 (certificate pinning)。}

WinHTTP 异步状态机注意事项

WinHTTP 异步状态机在主机与 Windows PC 上相同。要为一个或多个通知注册回调函数,请使用 WinHttpSetStatusCallback 函数。出于调试目的,建议为 dwNotificationFlags 参数使用 WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS 标志,因为 WinHTTP 对其运行情况相对详细透明。虽然大多数通知不需要你做任何操作,但记录数据有助于发现问题的根本原因。 WinHTTP 使用单一线程发送通知。游戏应尽可能避免阻塞任何通知函数,否则会导致进程中所有 HTTP 请求都无法推进。未处理的通知也会增加内核态内存,可能导致崩溃。 WinHTTP 不会复制你的发送或接收缓冲区,要求你在对应的完成回调到达之前保持这些缓冲区处于分配状态。请务必从调用 WinHttpSendRequest 起,直到收到相应的 WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE 通知之前,保持发送缓冲区处于分配且有效的状态。同样地,每次调用 WinHttpReadData 后,都要保证接收缓冲区在收到对应的 WINHTTP_CALLBACK_STATUS_READ_COMPLETE 通知之前保持分配且有效。我们还建议接收缓冲区大小至少为 8 KB,以避免可能导致栈耗尽的递归问题。 游戏还应确保 WinHTTP 缓冲区能正确清空,方法是持续进行异步的 WinHttpQueryDataAvailable/WinHttpReadData 循环,并且不长时间阻塞 WinHTTP 回调。

验证 TLS (Transport Layer Security)/SSL (Secure Sockets Layer) 握手

作为安全最佳实践,游戏应对 TLS/SSL 握手进行验证,并只使用 TLS 1.2。 额外的验证在 WINHTTP_CALLBACK_STATUS_SENDING_REQUEST 通知中进行。在此通知中,必须调用 XNetworkingVerifyServerCertificate 函数并传入先前对 XNetworkingQuerySecurityInformationForUrlUtf16Async 相应调用中获取的 XNetworkingSecurityInformation 结构。如果证书链无效,此函数会失败。你应在回调完成之前 立即 关闭 WinHTTP 句柄,确保没有数据被传输到或来自已被入侵的服务器。 除验证证书链外,XNetworkingVerifyServerCertificate 函数也是主机端 Fiddler 功能所必需的。

调试 WinHTTP

Fiddler 是查看和调试 WinHTTP 流量的实用工具。为使 Fiddler 能捕获你的游戏流量,你必须为 WinHttpOpen 传入 WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXYWINHTTP_NO_PROXY_NAMEWINHTTP_NO_PROXY_BYPASS 标志。还必须在 WINHTTP_CALLBACK_STATUS_SENDING_REQUEST 通知回调中调用 XNetworkingVerifyServerCertificate
HTTP Monitor 不能用于 Microsoft 游戏开发工具包 (GDK) 游戏。

参考 API 文档

另请参阅

Windows HTTP Services (WinHTTP) XSAPI C 概述 (安全链接) XUser 在 Partner Center 中设置 Web 服务 (NDA 文章) XBOX One 主机上的 Fiddler 通信安全最佳实践概述 (NDA 文章)
最后修改于 2026年8月24日