news 2026/9/25 5:02:22

Wi-Fi Direct Services UWP 示例详解:在 Windows 10 上发布与发现 Wi-Fi Direct 服务

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Wi-Fi Direct Services UWP 示例详解:在 Windows 10 上发布与发现 Wi-Fi Direct 服务
  • 示例工程

【免费下载链接】Windows-universal-samples

API samples for the Universal Windows Platform.

项目地址:https://gitcode.com/gh_mirrors/wi/Windows-universal-samples
点击查看免费下载

本文以 Windows-universal-samples 仓库中的 WiFiDirectServices 示例 为核心,系统讲解如何在 UWP 应用中基于Windows.Devices.WiFiDirect.ServicesAPI 实现服务的发布(Advertise)、发现(Discover)、连接(Connect)与数据传输(Send Data)。读完本文,你将掌握 Wi-Fi Direct Services API 的完整调用链、广告方(Advertiser)与发现方(Seeker)两种角色的实现要点,以及如何在实际设备上构建、部署并验证这套点到点通信方案。

什么是 Wi-Fi Direct Services:基于 "Enable" 服务的自定义服务

Wi-Fi Direct Services API 依据 Wi-Fi 联盟(Wi-Fi Alliance)规范提供"Enable" 服务,它允许开发者定义自定义 "服务"(service),由一台设备对外广播(advertise),另一台设备发现(discover)并连接。与传统的 Wi-Fi Direct(仅建立设备间连接)不同,Services 层在连接之上抽象出"服务名"与"服务信息"的概念,使两台设备可以基于约定的服务名而不是 MAC 地址来互相发现和通信。

从示例的 Package.appxmanifest 可以看到,运行该示例需要两项关键声明:

<Capabilities> <Capability Name="internetClientServer" /> <DeviceCapability Name="proximity" /> </Capabilities>
  • internetClientServer:允许应用使用 TCP/UDP socket 进行网络通信(Wi-Fi Direct 会话建立后通过 socket 传数据);
  • proximity:Wi-Fi Direct 等近距离无线通信所需的设备功能声明,缺少它将无法调用 Services API。

同时清单将目标设备族声明为Windows.Universal,最小版本10.0.10240.0,即面向 Windows 10 及更高版本。

重要硬件前提:本示例需要两台或更多搭载支持 Wi-Fi Direct services 的 Wi-Fi 芯片与驱动的设备。单台设备无法完成"发布→发现→连接"的闭环验证。

示例功能全景:五个可运行场景

示例以SDKTemplate通用框架组织 UI,在 SampleConfiguration.cs 中注册了 5 个场景(Scenario),完整覆盖了 Wi-Fi Direct Services 的两端角色:

场景名称角色核心能力
Scenario1Create Advertiser广告方配置并启动服务发布
Scenario2Advertiser Accept广告方接受/拒绝接入请求、管理已连接会话
Scenario3Discover/Connect Services发现方按服务名发现服务并建立连接
Scenario4Select Session (Seeker)发现方查看/关闭已建立的会话
Scenario5Send Data双方通过 TCP/UDP socket 收发数据

示例把全部业务逻辑收敛在两个核心文件中:状态管理与 API 调用的 WiFiDirectServiceManager.cs,以及各类 API 对象封装与事件处理的 WiFiDirectServiceWrappers.cs(C++ 版本对应 WiFiDirectServicesManager.cpp 与 WiFiDirectServicesWrappers.cpp)。UI 层只负责收集用户输入并调用管理器,这种分层正是 UWP 示例的标准做法。

场景一:发布一个服务(Advertiser Create)

服务发布的核心类是WiFiDirectServiceAdvertiser。在 WiFiDirectServiceManager.cs 的StartAdvertisement方法中,可以看到完整的配置项,下面逐一说明其约束与含义。

服务名(ServiceName)的命名约束

WiFiDirectServiceAdvertiser advertiser = new WiFiDirectServiceAdvertiser(serviceName);

源码注释给出了三条硬性约束:

  • 服务名内部限制为UTF-8 编码下最多 255 字节;
  • 合法字符包括字母数字、.、-以及任意多字节字符;
  • 发现服务时a-z / A-Z 不区分大小写。

实际开发中建议使用类似反向域名(如com.example.games.foobar)的命名约定,保证跨应用可发现。

自动接受与会话建立模式

advertiser.AutoAcceptSession = autoAccept; advertiser.PreferGroupOwnerMode = preferGO;
  • AutoAcceptSession:为true时,服务在发现方发起连接后无需广告方人工干预即可建立会话。但注意:若连接所需的配置方法要求 PIN,则即使设置了自动接受,广告方也必须接受连接(PIN 需要人工核对)。
  • PreferGroupOwnerMode:是否倾向成为 P2P 连接的组所有者(Group Owner, GO)。示例默认勾选,源码注释解释了原因:GO 可以同时连接多个客户端,而作为客户端的设备只能连接一个 GO。因此把广告方设置为 GO,可以支撑一对多的连接拓扑。

服务状态与自定义状态码

advertiser.ServiceStatus = status; advertiser.CustomServiceStatusCode = customStatus;

WiFiDirectServiceStatus的默认值是Available(可用)。若业务需要表达"忙碌"或自定义状态,可选择Busy或Custom,此时通过CustomServiceStatusCode传入自定义状态码(大于 1 的值)。Scenario1_AdvertiserCreate.xaml.cs 展示了 UI 上如何把下拉选项映射为状态枚举,并把文本框内容解析为uint自定义码。

服务信息(ServiceInfo)与延迟会话信息(DeferredSessionInfo)

advertiser.ServiceInfo = serviceInfoDataWriter.DetachBuffer(); advertiser.DeferredSessionInfo = deferredSessionInfoDataWriter.DetachBuffer();

这两个缓冲区的语义不同,是示例注释中最值得注意的细节:

  • ServiceInfo(最多 65000 字节):随广播发布的服务信息。发现方可以显式地指定一个该缓冲区子集的短缓冲区来定向发现——若发现方提供的片段与广播内容匹配,则整个缓冲区会返回给发现方;若不匹配,服务信息不会返回。示例用字符串承载,但实际可以是任意数据。
  • DeferredSessionInfo(最多 144 字节):当连接被"延迟"(deferred,即未自动接受)时发给发现方的信息。它会在AutoAcceptSession = false、或连接需要 PIN 时被发送。发现方通过WiFiDirectService.SessionDeferred事件收到该信息(见 WiFiDirectServiceWrappers.cs 的OnSessionDeferred)。

两者都通过DataWriter写入InMemoryRandomAccessStream后DetachBuffer()得到IBuffer,这是 WinRT 中构造缓冲区的标准写法。

支持的配置方法(Configuration Methods)

advertiser.PreferredConfigurationMethods.Add(configMethod);

配置方法决定了会话建立时是否需要 PIN 以及 PIN 的输入方式,取值来自WiFiDirectServiceConfigurationMethod:

  • Default(WFD Services 默认):不需要显式输入 PIN,连接体验最无缝;
  • PinDisplay:广告方显示 PIN,发现方查看后输入;
  • PinEntry:发现方显示 PIN,广告方查看后输入。

示例 UI 中 Scenario1 允许同时勾选多种方法。源码注释给出典型组合建议:广告方通常支持 PinDisplay(及 Default),发现方用 PinEntry 或 Default 连接。

服务名前缀(Service Name Prefixes)

advertiser.ServiceNamePrefixes.Add(prefix);

广告方可以显式声明允许被前缀匹配发现的服务名前缀。示例 UI 支持动态添加/删除前缀列表,源码注释强调两点:

  • 每个前缀都会由驱动处理,因此支持的数量有限,应用只应发布发现所必需的前缀;
  • 前缀必须是服务名的真实前缀,否则发布会失败。例如服务名com.example.games.foobar,合法前缀是com.example.games。

启动与停止发布

advertiser.Start(); // 必须从 UI 线程调用 advertiser.Stop(); // 停止广播

Start()可能因驱动无法处理请求或设备不支持 Services 而抛出异常,示例会捕获并向用户报告(WiFiDirectServiceManager.cs)。注意:Start()必须从应用的 UI 线程调用。UnpublishService通过Stop()停止广播;包装类AdvertisementWrapper在Dispose()中也会在状态为Started时调用Stop()并注销事件处理器(WiFiDirectServiceWrappers.cs)。

广告方的三个关键事件由AdvertisementWrapper注册(WiFiDirectServiceWrappers.cs):

事件触发时机处理
AdvertisementStatusChanged服务创建/启动,或广播因任何原因停止状态为Aborted/Stopped时从列表移除广告
AutoAcceptSessionConnected自动接受的会话已连接包装会话并加入会话列表
SessionRequested收到必须显式接受/拒绝的会话请求展示 PIN 提示,等待用户操作

场景二:接受或拒绝接入请求(Advertiser Accept)

当广告方未开启自动接受、或连接需要 PIN 时,SessionRequested事件触发,请求进入WiFiDirectServiceSessionRequest队列。AdvertisementWrapper会解析请求中的SessionInfo(发现方随请求发送的信息),并在ProvisioningInfo.IsGroupFormationNeeded && SelectedConfigurationMethod == PinDisplay时提示广告方"远端应输入 PIN:xxx"(WiFiDirectServiceWrappers.cs)。

接受连接:PIN 的三种判定分支

AcceptSessionRequest(WiFiDirectServiceWrappers.cs)按ProvisioningInfo分三条路径处理 PIN:

if (request.ProvisioningInfo.IsGroupFormationNeeded && request.ProvisioningInfo.SelectedConfigurationMethod == WiFiDirectServiceConfigurationMethod.PinDisplay) { pinForConnect = this.pin; // 广告方展示的 PIN,发现方输入 } else if (request.ProvisioningInfo.IsGroupFormationNeeded && request.ProvisioningInfo.SelectedConfigurationMethod == WiFiDirectServiceConfigurationMethod.PinEntry) { if (pin.Length == 0) throw new ArgumentException("Expected PIN for connection, not provided"); pinForConnect = pin; // 发现方展示的 PIN,广告方输入 } else { pinForConnect = ""; // 无需 PIN }

随后调用advertiser.ConnectAsync(request.DeviceInformation[, pinForConnect])完成会话建立。该调用同样必须来自 UI 线程。

拒绝连接

拒绝的实现很简洁:直接request.Dispose()即可(WiFiDirectServiceWrappers.cs),随后请求会从队列和 UI 中移除。

场景三:发现服务并建立连接(Seeker Discover)

发现方一侧的核心是WiFiDirectService.GetSelector与DeviceInformation.FindAllAsync,实现在 WiFiDirectServiceManager.cs 的DiscoverServicesAsync中。

构造设备选择器

if (requestedServiceInfo == "") { serviceSelector = WiFiDirectService.GetSelector(serviceName); // 仅按服务名搜索 } else { serviceSelector = WiFiDirectService.GetSelector(serviceName, serviceInfoDataWriter.DetachBuffer()); // 按服务名+服务信息片段搜索 }

GetSelector有两个重载:只传服务名时按名发现;额外传入IBuffer时,会在发现过程中尝试匹配广播方的ServiceInfo——匹配规则与广告方侧一致:发现方提供的短缓冲区是广播方服务信息缓冲区的子集时,完整信息才会返回。

请求附加属性并执行发现

DeviceInformationCollection deviceInfoCollection = await DeviceInformation.FindAllAsync(serviceSelector, additionalProperties);

示例通过FindAllAsync一次性完成发现并返回列表,同时请求了 5 个 Wi-Fi Direct Services 相关的系统属性(ServiceAddress、ServiceName、ServiceInformation、AdvertisementId、ServiceConfigMethods)。DiscoveredDeviceWrapper.ParseProperties(WiFiDirectServiceWrappers.cs)把这些属性解析为强类型字段,其中ServiceAddress是 MAC 地址字节数组,会被格式化为XX:XX:XX:XX:XX:XX字符串。

源码注释还提到一个进阶替代方案:DeviceWatcher可以在服务一被发现就持续推送更新,直到显式停止,适合需要"持续在线发现"的应用;FindAllAsync则适合一次性扫描。

连接的两种方式

Scenario3_SeekerDiscover.xaml.cs 的注释总结了发现方的两条连接路径,两条路径都先经过WiFiDirectService.FromIdAsync(deviceInfo.Id)打开服务对象(见OpenSessionAsync,WiFiDirectServiceWrappers.cs):

  1. 先获取预配置信息再连接:调用GetProvisioningInfoAsync(configMethod)判断是否需要组建群组、是否需要 PIN:
    • 需要 PIN(IsGroupFormationNeeded且配置方法为PinEntry/PinDisplay)时,ConnectAsync(pin)传入 PIN;
    • 不需要 PIN 时,直接ConnectAsync()。
  2. 直接连接:不查询预配置信息,直接ConnectAsync(),此时优先使用 WFD Services 默认配置方法(Default)。

示例 UI 允许发现方预先设置两个选项(SetServiceOptionsAsync,WiFiDirectServiceWrappers.cs):

  • PreferGroupOwnerMode:本端是否倾向成为组所有者;
  • SessionInfo:随连接请求发送给广告方的可选 144 字节信息缓冲区(与广告方的DeferredSessionInfo对应)。

GetProvisioningInfoAsync的返回值WiFiDirectServiceProvisioningInfo携带两个关键字段:IsGroupFormationNeeded(是否需要组建 P2P 群组)与SelectedConfigurationMethod(最终选定的配置方法),发现方据此决定是"显示 PIN"还是"输入 PIN"(WiFiDirectServiceWrappers.cs)。

场景四与场景五:会话管理与 TCP/UDP 数据传输

会话对象与会话状态

连接成功后得到WiFiDirectServiceSession,示例用SessionWrapper封装(WiFiDirectServiceWrappers.cs),暴露会话的四个身份字段:

  • AdvertisementId:广告实例 ID;
  • SessionId:会话 ID;
  • ServiceAddress:服务地址(MAC 格式);
  • SessionAddress:会话地址。

SessionWrapper注册了SessionStatusChanged(状态变为Closed时自动从管理器清理会话)和RemotePortAdded(对端打开端口时触发)两个事件,并在Close()中先释放所有 socket 再Dispose()会话,用AutoResetEvent等待关闭确认(正常情况下 5 秒内完成)。

在会话上打开 TCP/UDP 端口

SessionWrapper提供两个方法向会话注册本端端口:

  • AddStreamSocketListenerAsync(port)(TCP):创建StreamSocketListener,通过session.GetConnectionEndpointPairs()[0].LocalHostName绑定到会话本地地址,然后调用session.AddStreamSocketListenerAsync(listenerSocket)把监听器注册到会话上(WiFiDirectServiceWrappers.cs);
  • AddDatagramSocketAsync(port)(UDP):创建DatagramSocket,绑定后调用session.AddDatagramSocketAsync(socket)注册。源码注释特别说明:示例中该 socket 是"只读"的——应用先启动监听,等远端发数据(WiFiDirectServiceWrappers.cs)。

关键机制:当对端调用AddStreamSocketListenerAsync/AddDatagramSocketAsync注册端口时,本端会触发RemotePortAdded事件。OnRemotePortAdded(WiFiDirectServiceWrappers.cs)根据args.Protocol是Tcp还是Udp,创建StreamSocket或DatagramSocket并通过ConnectAsync(endpointPairCollection[0])主动连向对端端口,完成"端口登记 + 主动连接"的双向打通。

消息协议:长度前缀 + 字符串

SocketWrapper(WiFiDirectServiceWrappers.cs)统一封装了 TCP 与 UDP 的消息收发,传输格式为:uint32 长度前缀 + UTF-8 字符串。

writer.WriteUInt32(writer.MeasureString(message)); writer.WriteString(message); await writer.StoreAsync();

接收端HandleReceivedMessage先LoadAsync(sizeof(uint))读长度,再按长度加载并解码字符串;TCP 是流式协议,读完一条后会递归继续读下一条;UDP 则通过DatagramSocket.MessageReceived事件回调处理(load = false分支),每次事件对应一条完整消息。这正是示例能"持续互发文本"的底层原因。

双向的 TCP 连接接纳

在广告方一侧,StreamSocketListenerWrapper监听ConnectionReceived事件,一旦发现方连入,就把收到的StreamSocket交给SessionWrapper.AddStreamSocketInternal,随即启动递归接收循环并加入 socket 列表(WiFiDirectServiceWrappers.cs)。至此,两端各持有可读可写的 socket,即可互发文本消息。

系统要求

依据 README.md 的系统要求部分:

  • Client:Windows 10
  • Server:Windows Server 2016 Technical Preview
  • Phone:Windows 10
  • 硬件:两台或更多搭载支持 Wi-Fi Direct services 的 Wi-Fi 芯片与驱动的设备(除非用本示例与其他支持 Wi-Fi Direct services 的设备交互)

构建与运行

示例提供 C#、C++(cppcx)两种实现,各自包含完整的.sln解决方案(cs/WiFiDirectServices.sln、cpp/WiFiDirectServices.sln),以及对应的项目文件(WiFiDirectServices.csproj、WiFiDirectServices.vcxproj)。

构建步骤:

  1. 若下载的是示例集合 ZIP,务必解压整个压缩包,而不是只解压本示例所在文件夹——示例依赖仓库根目录的SharedContent共享资源(README 的 front matter 中extendedZipContent已声明该依赖)。
  2. 启动 Visual Studio,选择文件 → 打开 → 项目/解决方案。
  3. 定位到Samples/WiFiDirectServices下的cs或cpp子文件夹,双击.sln文件。
  4. 按Ctrl+Shift+B或选择生成 → 生成解决方案。

运行步骤:

  • 仅部署:选择生成 → 部署解决方案;
  • 部署并调试运行:按F5或选择调试 → 开始调试;不调试直接运行按Ctrl+F5或选择调试 → 开始执行(不调试)。

验证提示:要真正完成"发现并连接服务"的闭环,至少需要把示例部署到两台设备上;单台设备只能观察发布/发现过程,无法建立会话。两台设备可分别扮演广告方(Scenario1→Scenario2)与发现方(Scenario3→Scenario4),最后任选一端在 Scenario5 中打开 TCP 或 UDP 端口互发文本。

关键实现文件索引

以下文件是本示例的核心,便于进一步阅读源码:

  • cs/WiFiDirectServiceManager.cs:单例管理器,封装发布、发现、会话列表与 UI 状态同步;
  • cs/WiFiDirectServiceWrappers.cs:AdvertisementWrapper、DiscoveredDeviceWrapper、SessionWrapper、SocketWrapper等 API 对象封装与事件处理;
  • cs/Scenario1_AdvertiserCreate.xaml.cs:广告方配置 UI 与参数收集;
  • cs/Scenario3_SeekerDiscover.xaml.cs:发现方 UI,含两条连接路径;
  • cs/Package.appxmanifest:proximity与internetClientServer能力声明;
  • cs/SampleConfiguration.cs:五个场景的注册与导航;
  • cpp/WiFiDirectServicesManager.cpp 与 cpp/WiFiDirectServicesWrappers.cpp:C++(cppcx)版本对应实现;
  • cs/Helpers/BufferConverter.cs 与 cs/Helpers/ServiceStatusConverter.cs:缓冲区与状态枚举的转换辅助类。

整体来看,这个示例完整演示了 Wi-Fi Direct Services 从"服务命名 → 广播发布 → 按名发现 → 预配置协商(PIN)→ 会话建立 → TCP/UDP 数据通道"的整条链路。对于需要构建近距离、无路由器场景下点对点通信应用的开发者,这套 API 与示例代码是很好的起点。

  • 示例工程

【免费下载链接】Windows-universal-samples

API samples for the Universal Windows Platform.

项目地址:https://gitcode.com/gh_mirrors/wi/Windows-universal-samples
点击查看免费下载
上一篇:Notepad--:跨平台文本编辑器的终极解决方案,打造你的专属代码工作台
下一篇:Step-Audio语音合成可扩展性设计:支持百万级用户的架构方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 5:02:07

五谷丰登婚宴酒店口碑怎么样,客户评价如何

三十一年深耕餐饮赛道&#xff0c;十三载稳定服务大众宴席&#xff0c;在邯郸本土婚宴市场的发展变迁中&#xff0c;总有一个熟悉的品牌身影陪伴着一对对新人走进婚姻的殿堂。从街边小店的家常菜经营&#xff0c;到覆盖邯郸多区县的连锁婚宴品牌&#xff0c;邯郸市复兴区五谷丰…

作者头像 李华
网站建设 2026/9/25 5:02:06

宁波市靠谱的橡胶密封件制造厂家推荐,一站式密封解决方案实力参考

宁波市泓泰橡胶科技有限公司位于东海之滨——浙江省宁波市&#xff0c;是一家集研发、生产、销售于一体的橡胶密封件制造商&#xff0c;始终秉持客户至上的经营理念&#xff0c;依托经验丰富的技术团队和成熟的生产工艺&#xff0c;为客户提供从材料选型、模具开发到批量交付的…

作者头像 李华
网站建设 2026/9/25 5:01:59

如何用Docker一键部署MindSpeed LLM:昇腾镜像构建指南

如何用Docker一键部署MindSpeed LLM&#xff1a;昇腾镜像构建指南 【免费下载链接】MindSpeed-LLM 昇腾LLM分布式训练框架 项目地址: https://gitcode.com/Ascend/MindSpeed-LLM MindSpeed LLM 是昇腾大语言模型分布式训练框架&#xff0c;支持分布式预训练、指令微调、…

作者头像 李华
网站建设 2026/9/25 5:01:08

ESP32双OTA分区实现应用平台:固件安装、启动切换与回退实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 5:01:04

安路TD与Modelsim联合仿真:IP核编译、库映射与波形调试避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 5:01:01

diagrams.net在线画图实操技:从架构图到流程图的高效工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华