- 示例工程
【免费下载链接】Windows-universal-samples
API samples for the Universal Windows Platform.
本文以 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 的两端角色:
| 场景 | 名称 | 角色 | 核心能力 |
|---|---|---|---|
| Scenario1 | Create Advertiser | 广告方 | 配置并启动服务发布 |
| Scenario2 | Advertiser Accept | 广告方 | 接受/拒绝接入请求、管理已连接会话 |
| Scenario3 | Discover/Connect Services | 发现方 | 按服务名发现服务并建立连接 |
| Scenario4 | Select Session (Seeker) | 发现方 | 查看/关闭已建立的会话 |
| Scenario5 | Send 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):
- 先获取预配置信息再连接:调用
GetProvisioningInfoAsync(configMethod)判断是否需要组建群组、是否需要 PIN:- 需要 PIN(
IsGroupFormationNeeded且配置方法为PinEntry/PinDisplay)时,ConnectAsync(pin)传入 PIN; - 不需要 PIN 时,直接
ConnectAsync()。
- 需要 PIN(
- 直接连接:不查询预配置信息,直接
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)。
构建步骤:
- 若下载的是示例集合 ZIP,务必解压整个压缩包,而不是只解压本示例所在文件夹——示例依赖仓库根目录的
SharedContent共享资源(README 的 front matter 中extendedZipContent已声明该依赖)。 - 启动 Visual Studio,选择文件 → 打开 → 项目/解决方案。
- 定位到
Samples/WiFiDirectServices下的cs或cpp子文件夹,双击.sln文件。 - 按
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.
相关推荐
Wi-Fi Direct UWP 示例详解:设备广播发现、配对连接与套接字数据传输(Windows-universal-samples)
Wi Fi Direct UWP 示例详解:设备广播发现、配对连接与套接字数据传输(Windows universal samples) 导读 本文围绕 Win
示例工程探索Wi-Fi Direct:叶子C的开源项目WifiP2P
探索Wi Fi Direct:叶子C的开源项目WifiP2P 在数字化时代,无线通信技术不断演变,其中Wi Fi Direct是一个极具潜力的技术,它允许设备间
Multitarget-tracker ByteTrack算法实现:高精度多目标跟踪新选择
Multitarget tracker ByteTrack算法实现:高精度多目标跟踪新选择 Multitarget tracker是一款基于匈牙利算法和卡尔曼滤
计算机视觉视频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考