- 示例工程
【免费下载链接】Windows-classic-samples
This repo contains samples that demonstrate the API used in Windows classic desktop applications.
导读
本文围绕 Windows-classic-samples 仓库中的 WDS Multicast 消费者示例(Samples/WindowsDeploymentServices/Multicast/Consumer)展开,讲解如何借助 Windows Deployment Services(WDS)的 Multicast Client 库,连接 WDS Transport Server 上的组播会话、接收由组播提供方(content provider)编码的数据块并写入本地磁盘。读完本文,你将掌握tmc消费者程序的完整调用链(WdsTransportClientInitialize→StartSession→ 回调处理 → 收尾清理)、三个关键回调的实现要点,以及结合仓库内配套的组播提供方与传输管理器示例搭建端到端组播传输方案的完整步骤。
一、背景:WDS 传输平台与“消费者 / 提供方”双组件模型
Windows Deployment Services 传输平台允许应用程序通过网络组播传输任意类型的数据。一次完整的自定义组播传输由一对组件配合完成:
- 内容提供方(content provider):运行在 WDS Transport Server 侧,把要传输的数据编码成一连串数据块。仓库中的配套示例位于 Samples/WindowsDeploymentServices/Multicast/WdsProvider。
- 内容消费者(content consumer):运行在客户端侧,负责连接组播会话并解释这些数据块。本文讲解的示例即是消费者这一侧的最小实现。
原文档明确指出了职责划分:It is the content consumer's responsibility to interpret these blocks of data。也就是说,提供方只负责“切块编码”,而“怎么理解收到的数据”完全由消费者决定。仓库提供的消费者示例是一个最简实现——它把收到的数据原样落盘;真实应用如需按自有格式解释数据,只需要重写示例中的ReceiveContentsCallback回调即可。
二、仓库结构与工程组织
消费者示例由以下文件组成(对应源码目录 Samples/WindowsDeploymentServices/Multicast/Consumer/cpp):
| 文件 | 作用 |
|---|---|
main.cpp | 应用主程序,演示 WDS Multicast Client API 的完整调用流程 |
consumer.vcxproj | Visual Studio 工程文件,定义了平台、配置与链接依赖 |
consumer.sln | 解决方案文件,加载后即可生成可执行程序 |
wdsscon.rc | Windows 资源文件(版本信息等) |
README.txt | 命令行用法与端到端方案搭建说明 |
工程配置上有两点值得注意(见 consumer.vcxproj):
- 链接依赖:Debug/Release 配置均通过
AdditionalDependencies引入wdstptc.lib(WDS Transport Client 导入库),这是使用组播客户端 API 的前提; - 平台工具集:
PlatformToolset为v110(对应 Visual Studio 2012 时代),字符集为 Unicode,控制台子系统(_CONSOLE)。
三、核心调用链:一个组播下载会话的生命周期
消费者程序的主流程全部位于 main.cpp 的wmain中。整个会话的生命周期可以拆成六个阶段:
1. 初始化客户端库
在任何组播客户端调用之前,必须先调用WdsTransportClientInitialize()(对应源码 main.cpp)。这是整个库的全局初始化入口。
2. 准备本地输出文件
程序用CreateFile以CREATE_ALWAYS、GENERIC_WRITE | GENERIC_READ、FILE_SHARE_READ的方式打开命令行第 4 个参数指定的本地路径(main.cpp)。这个文件句柄同时被用作会话的 caller data,在后续回调中通过pCallerData传回,供数据落盘使用。
3. 填充请求参数并初始化会话
WDS_TRANSPORTCLIENT_REQUEST结构体定义了本次传输的全部参数(main.cpp):
| 成员 | 示例代码中的取值 | 含义 |
|---|---|---|
ulLength | sizeof(RequestParams) | 结构体长度,供版本校验 |
ulApiVersion | WDS_TRANSPORTCLIENT_CURRENT_API_VERSION | 客户端使用的 API 版本 |
ulAuthLevel | WDS_TRANSPORTCLIENT_AUTH | 认证级别(要求认证) |
pwszServer | argv[1] | 组播服务器的主机名或 IP |
pwszNamespace | argv[2] | 服务器上的命名空间 |
pwszObjectName | argv[3] | 命名空间暴露的远端内容名称 |
ulProtocol | WDS_TRANSPORTCLIENT_PROTOCOL_MULTICAST | 传输协议,固定为组播 |
pvProtocolData/ulProtocolDataLength | NULL/0 | 协议附加数据(本例不使用) |
ulCacheSize | 0x2000000(即 32 MB) | 客户端接收缓存 / 未确认数据上限 |
关于ulCacheSize,源码注释特别强调(main.cpp):缓存大小并没有硬性标准,应针对不同取值做实测,在传输性能与应用内存占用之间取平衡。其工作机制在ReceiveContentsCallback的注释中有明确解释:若消费者收到数据后不调用WdsTransportClientCompleteReceive释放缓存,客户端会在交付的数据量达到该上限时停止接收。
随后调用WdsTransportClientInitializeSession(&RequestParams, hFile, &hMCClientKey)(main.cpp)。注意它的语义:这一步只初始化会话,不会真正开始传输。
4. 注册三个回调
回调必须在启动传输之前注册完毕。程序依次注册(main.cpp):
WDS_TRANSPORTPROVIDER_SESSION_COMPLETE(对应SessionCompleteCallback);WDS_TRANSPORTCLIENT_SESSION_START(对应SessionStartCallback);WDS_TRANSPORTCLIENT_RECEIVE_CONTENTS(对应ReceiveContentsCallback)。
5. 启动会话并等待完成
WdsTransportClientStartSession(hMCClientKey):真正启动传输(main.cpp);WdsTransportClientWaitForCompletion(hMCClientKey, INFINITE):同步阻塞直至会话结束(main.cpp);WdsTransportClientQueryStatus(hMCClientKey, &ulStatus, &ulError):查询最终状态码与错误码(main.cpp)。
6. 清理与退出
无论成功还是失败,exit标签统一执行收尾(main.cpp):
- 关闭文件句柄
CloseHandle(hFile); - 若
hr失败,先调用WdsTransportClientCancelSession取消会话; - 调用
WdsTransportClientCloseSession关闭会话句柄; - 最后调用
WdsTransportClientShutdown()释放整个客户端库。
整段代码还大量使用了三个统一错误处理宏EXIT_ON_HR_ERROR/EXIT_ON_MALLOC_ERROR/EXIT_ON_WIN_ERROR(main.cpp),出错时打印0x%08x格式的 HRESULT 与出错行号并跳转清理,这是值得借鉴的样板写法。
四、三个回调的职责与实现细节
1. SessionStartCallback —— 会话开始通知
对应PFN_WdsTransportClientSessionStart原型(main.cpp),在组播会话开始时被调用,用于向消费者传递服务端侧的元信息(最典型的是FileSize,即文件总大小)。示例仅打印File transfer started;真实应用可在此处做磁盘空间预检、进度条初始化等工作。
2. ReceiveContentsCallback —— 数据消费的核心
这是整个示例的灵魂,对应PFN_WdsTransportClientReceiveContents原型(main.cpp)。原型参数含义如下:
hSessionKey:所属会话的句柄;pCallerData:启动会话时传入的调用方数据(本例即文件句柄);pContents/ulSize:收到的数据块缓冲及其字节数。该缓冲区归属客户端库,不应被修改;如需延长其生命周期,可调用WdsTransportClientReferenceBuffer;pContentOffset:该数据块在数据流中的起始偏移。
示例的实现逻辑是:
- 把
pContentOffset的LowPart/HighPart填入OVERLAPPED结构(main.cpp); - 用
WriteFile以重叠 I/O 方式把数据块写入本地文件(main.cpp); - 调用
WdsTransportClientCompleteReceive(hSessionKey, ulSize, pContentOffset)通知客户端释放与该次写入关联的缓存(main.cpp)。这一步同时承担**接收节流(throttling)**职责——不调用它,客户端在未确认数据量达到ulCacheSize后就会停住; - 写盘失败时调用
WdsTransportClientCancelSession取消整个传输(main.cpp)。
原文档特别指出:真实应用要做的正是重写此函数——把“落盘”换成“按业务格式解释pContents中的数据”。
3. SessionCompleteCallback —— 会话结束通知
对应PFN_WdsTransportClientSessionCompete原型(main.cpp),表示客户端不会再向消费者发送任何回调,会话要么成功、要么遇到了不可恢复的错误。dwError为ERROR_SUCCESS表示传输成功,否则为对应的错误码。示例仅打印结果;真实应用可在此处做结果上报、日志记录等工作。
五、命令行用法与示例
程序是一个控制台应用,用法与各参数含义见 README.txt:
consumer.exe <server> <namespace> <remote file> <local file>| 参数 | 说明 |
|---|---|
server | 组播服务器的主机名或 IP 地址 |
namespace | 服务器上用户定义的命名空间 |
remote file | 该命名空间在服务器上暴露的内容名称 |
local file | 本地保存文件的路径 |
示例调用(程序内部校验argc < 5即打印用法并退出,见 main.cpp):
consumer.exe MyWdsServer Namespace1 DataFile.dat c:\LocalFile.dat传输结束后程序会输出完成时间统计:Download Completed - Time:%.2f Seconds(main.cpp)。
六、编译与运行
原文档给出的构建步骤(适用于本仓库中的工程文件):
- 启动 Visual Studio,选择File > Open > Project/Solution;
- 进入示例目录,双击
consumer.sln; - 按 F7(Visual Studio 2013 为 F6)或使用Build > Build Solution编译。
运行方式:
- 调试运行:F5 或Debug > Start Debugging;
- 直接运行:Ctrl+F5 或Debug > Start Without Debugging。
需要说明的适用前提:原文档标注的系统要求为客户端 Windows 8.1、服务器 Windows Server 2012 R2;工程文件使用v110工具集并链接wdstptc.lib,因此构建环境需具备相应 Windows SDK / WDS 传输客户端库。README 中提到的外部文档链接在本仓库内不可用,实践时以本地 SDK 头文件(如wdstci.h)和文档为准。
七、端到端方案:把消费者接入真实组播传输
消费者只是“一对组件”中的一半。原文档给出了完整落地四步走,结合仓库内配套示例可归纳如下:
- 构建/选择一个内容提供方 DLL,把数据编码成该消费者能理解的格式。仓库配套示例位于 Samples/WindowsDeploymentServices/Multicast/WdsProvider,它实现了一个把服务器文件切块编码的提供方;
- 安装并注册提供方 DLL:把 DLL 部署到 WDS Transport Server 上并完成注册。该提供方的 README.txt 指出:注册是一次性过程,完成后需重启 WDS Transport Server 服务;注册可调用 WdsTptMgmt API 的
IWdsTransportSetupManager::RegisterContentProvider完成; - 创建传输命名空间(Namespace):通过 WDS 传输管理 API(WdsTptMgmt)在服务器上创建命名空间,指定使用上述内容提供方以及待组播传输的文件所在文件夹。仓库配套的管理端示例位于 Samples/WindowsDeploymentServices/Management/WDSTransportManager,其 README 说明该 API 支持启用/禁用传输服务、增删命名空间,并指出调用方需是 WDS 服务器上的管理员;
- 用消费者程序下载文件:即
consumer.exe <server> <namespace> <remote file> <local file>。
提供方侧的实现线索(便于对照理解数据流)
与消费者配套的提供方示例(main.c)从服务端角度印证了同一套双组件协议:
- 导出函数仅
WdsTransportProviderInit(见 wdscp.def),由服务器在加载 DLL 时调用; - 通过
WdsTransportServerRegisterCallback向服务器注册 9 个回调(main.c),涵盖实例创建/关闭、内容打开/读取/关闭、内容大小查询、访问检查等; WdsTransportProviderOpenContent以FILE_FLAG_OVERLAPPED打开待传输文件,并通过BindIoCompletionCallback绑定异步读完成例程CompleteRead(main.c);WdsTransportProviderReadContent从指定偏移发起重叠读,完成后由CompleteRead调用WdsTransportServerCompleteRead交还服务器(main.c)。
也就是说:提供方按偏移把文件切成块交给服务器组播分发,消费者按偏移把收到的块写回文件——两头通过“偏移 + 数据块”对齐,这正是该示例对(tmc消费者 +provider提供方)能互相配合的协议基础。真实业务只需把提供方的“读文件”和消费者的“写文件”替换为各自的编码/解码逻辑即可承载任意格式的数据。
八、小结
本文从 Samples/WindowsDeploymentServices/Multicast/Consumer/README.md 出发,结合源码梳理了 WDS 组播消费者的完整实现:双组件模型、WDS_TRANSPORTCLIENT_REQUEST参数、六个阶段的 API 调用链、三个回调的职责与ulCacheSize节流机制,并给出了命令行用法、构建步骤及接入仓库内提供方/传输管理示例的端到端方案。若要在实际业务中落地,核心改造点只有一个——重写ReceiveContentsCallback,把“落盘”换成你自己的数据解释逻辑。
- 示例工程
【免费下载链接】Windows-classic-samples
This repo contains samples that demonstrate the API used in Windows classic desktop applications.
相关推荐
Windows 部署服务(WDS)图像枚举示例:基于 WDS Client API 打造自定义部署客户端
Windows 部署服务(WDS)图像枚举示例:基于 WDS Client API 打造自定义部署客户端 本文以 Windows classic samples
示例工程Windows Web Services 自定义消息头实战:HttpCustomHeaderPurchaseOrderClient 服务代理示例全解析
Windows Web Services 自定义消息头实战:HttpCustomHeaderPurchaseOrderClient 服务代理示例全解析 本篇文章
示例工程ns-emu-tools 前端组件自动导入机制详解:基于 unplugin-vue-components 的 Vue 3 组件化实践
ns emu tools 前端组件自动导入机制详解:基于 unplugin vue components 的 Vue 3 组件化实践 本文以 ns emu to
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考