深入理解Wi-Fi P2P

2023-05-16

第7章 深入理解Wi-Fi P2P
本章所涉及的源代码文件名及位置
·W ifiP2pSettings.java
packages/ apps/ Settings/ src/ com/ android/ settings/ wifi/ p2p/ W ifiP2pSettings.java
·W ifiP2pService.java
frameworks/ base/ wifi/ java/ android/ net/ wifi/ p2p/ W ifiP2pService.java
·p2p_supplicant.c
external/ wpa_supplicant_8/ wpa_supplicant/ p2p_supplicant.c
·driver.h external/ wpa_supplicant_8/ src/ drivers/ driver.h
·p2p.c external/ wpa_supplicant_8/ src/ p2p/ p2p.c
·driver_nl80211.c external/ wpa_supplicant_8/ src/ drivers/ driver_nl80211.c
·ctrl_iface.c external/ wpa_supplicant_8/ wpa_supplicant/ ctrl_iface.c
·p2p_pd.c external/ wpa_supplicant_8/ src/ p2p/ p2p_pd.c
·p2p_go_neg.c external/ wpa_supplicant_8/ src/ p2p/ p2p_go_neg.c
7.1 概述
承接第6章介绍的W SC,本章将继续介绍W i-Fi联盟推出的另外一项重要技术规范W i-
Fi P2P。该规范的商品名为W i-Fi Direct,它支持多个W i-Fi设备在没有AP的情况下相互
连接。
在Android平台的W i-Fi相关模块中,P2P的功能点主要集中在:
·Android Framework中的W ifiP2pService,其功能和W ifiService类似,用于处理
和P2P相关的工作。
·wpa_supplicant中的P2P模块。
和W SC一样,本章的分析采用如下方法。
·首先介绍P2P所涉及的基础知识。
·然后分析和P2P相关的模块,包括Settings、W ifiP2pService以及W PAS。
7.2 P2P基础知识
W FA定义的P2P协议文档全名为"W i-Fi Peer-to-Peer(P2P)Technical
Specification",目前的版本为1.1,整个篇幅160页。P2P技术使得多个W i-Fi设备在没有
AP的情况下也能构成一个网络(P2P Network,也称为P2P Group)并相互通信。
W i-Fi P2P技术是W i-Fi Display ① 的基础。在Miracast应用场景中,一台支持P2P的
智能手机可直接连接上一台支持P2P的智能电视,智能手机随后将自己的视频,或者媒体资
源传送给电视机去显示或播放。显然,借助P2P技术,W i-Fi设备之间的直接相连将极大拓
展W i-Fi技术的使用场景。
注意 根据笔者的判断,随着越来越多的设备支持P2P和Miracast,智能终端设备之间
的多屏共享和互动功能将很快得以实现。另外,撰写本章之际,Google发布了Android
4.3。在这次发布盛会上,Google推出了ChromeCast设备。目前,ChromeCast的技术实
现细节还不清楚,据说是Google自己定义的Google cast协议(可参考
developers.google.com/ cast)。
下面先简单介绍一下P2P架构。
① 也称为Miracast,详情请参考作者的一篇博文
http:/ / blog.csdn.net/ innost/ article/ details/ 8474683。
7.2.1 P2P架构 [1]
P2P架构中定义了三个组件,笔者将其称为“一个设备,两种角色”,分别如下。
·P2P Device:它是P2P架构中角色的实体,可把它当做一个W i-Fi设备。
·P2P Group Owner:Group Owner(GO)是一种角色,其作用类似于
Infrastructure BSS中的AP。
·P2P Client:另外一种角色,其作用类似于Infrastructure BSS中的STA。
相信读者对上面这三个组件的概念并不陌生。实际上,P2P技术模仿了Infrastructure
BSS网络结构。在组建P2P Group(即P2P Network)之前,智能终端都是一个一个的
P2P Device。当这些P2P Device设备之间完成P2P协商后,其中将有一个并且只能有一
个 ① Device来扮演GO的角色(即充当AP),而其他Device来扮演Client的角色。
图7-1 P2P Group组织结构
图7-1展示了一个典型P2P Group的构成。和Infrastructure BSS类似,一个P2P
Group中只能有一个GO。一个GO可以支持1个或多个(即图中的1∶n)Client连接。
由于GO的功能类似于AP,所以周围不支持P2P功能的STA也能发现并关联到GO。这
些STA称为Legacy Client。
注意 “不支持P2P功能”更准确的定义是指不能处理P2P协议。在P2P网络中,GO等
同于AP,所以Legacy Client也能搜索到GO并关联上它。不过,由于Legacy Client不能
处理P2P协议,所以P2P一些特有功能在这些Legacy Client中无法实现。
通过上述介绍读者会进一步发现P2P Group和Infrastructure BSS的相似性。P2P
Device在构建P2P Group时,它将首先通过W SC来获取安全信息。然后,Client将利用协
商好的安全设置信息去关联 ② GO(即P2P Group中的AP)。
这部分内容和Infrastructure BSS中STA利用W SC先协商安全信息然后再关联至AP的
流程完全一样。正是这种相似性,使得P2P能充分利用现有的一些技术规范。图7-2所示为
P2P及其依赖的技术项。
图7-2 P2P及其依赖的技术项
为了保证一定的传输速率,P2P要求P2P Device必须支持802.11g及以上的规范。其
中,安全部分必须支持W PA2。由于P2P技术一个主要的应用场景就是设备之间共享媒体数
据(例如前面提到的Miracast应用场景),所以P2P Device还必须支持W MM(W i-Fi
MultiMedia的缩写,一种源自802.11e的QoS服务,主要针对实时视音频数据的传输)。
P2P Client关联到GO之前,需要先通过W SC来协商安全信息,所以W SC也是P2P的
依赖技术项。
在上述技术基础上,P2P规范定义了一些特有的技术项,图7-2列出了其中三种必须实
现的技术项,分别是P2P Discovery、P2P Group Operation以及P2P
PowerManagement。除了这三个必选技术项外,P2P规范还定义了一个可选技术项,名为
Managed P2P Device Operation,该技术项定义了在企业级环境中,如何由对应的IT部
门来统一配置和管理P2P设备。
在如图7-2所示的技术项中,P2P Discovery是P2P所特有的,也是其核心。本章将主
要围绕它进行介绍。首先来看P2P Discovery。
提示 P2P Group Operation讲的是GO如何管理一个Group,也就是GO的工作职
责。这部分内容请读者自行学习参考资料[2]。
P2P PowerManagement和P2P设备的电源管理有关,用于节省不必要的电力损耗。
由于篇幅关系,本章不讨论,感兴趣的读者自行学习参考资料[3]。
① 假设这设备只组成一个P2P Network。
② 注意,此处的关联指的是RSNA,其工作流程包括4-W ay Handshake。
7.2.2 P2P Discovery技术 [4]
P2P Discovery的作用很简单,就是使多个P2P Device能够互相发现并构建一个
Group。根据规范,它包括四个主要技术子项。
·Device Discovery:用于P2P设备搜索周围其他支持P2P的设备。
·Service Discovery:该Device Discovery基础上,P2P还支持搜索指定的服务。这
部分功能属于可选项,笔者觉得它和2.2.5节中提到的Bonjour类似。
·Group Formation:用于决定两个P2P Device谁来扮演GO,谁来扮演Client。
·P2P Invitation:用于激活一个Persistent Group(见下文解释),或者用于邀请
一个Client加入一个当前已存在的Group。
提示 Group分Persistent(永久性)Group和Temporary(临时性)Group两种。
我们举两个简单例子来说明二者的区别。
Temporary Group:当有文件要传给一个同事时,双方打开手机的W i-Fi P2P功能,
建立一个Group,然后传输文件,最后关闭W i-Fi P2P。在这个过程中,GO和Client的角
色分配由Group Formation来决定,这一次的GO可能是你的设备,下一次则可能是其他人
的设备。对于这种Group,在建立Group过程中所涉及的安全配置信息以及和Group相关的
信息(以后会见到它)都是临时的,即下一次再组建Group时,这些安全配置信息都将发生
变化。
Persistent Group:在这种Group中,GO由指定设备来扮演,而且安全配置信息及
Group相关信息一旦生成,后续就不会再发生变化(除非用户重新设置)。Persistent
Group中的GO多见于固定用途的设备,例如打印机等。如此,除了第一次通过P2P连接到
打印机时相对麻烦一点(需要利用W SC协商安全配置信息)外,后续使用的话,由于P2P
设备将保存这些安全信息,所以下次再使用打印机时就能利用这些信息直接和打印机进行关
联了。
由于篇幅关系,本章仅介绍上述四个知识点中最基础的Device Discovery和Group
Formation,而Service Discovery和P2P Invitation的内容请读者学习完本章后再仔细研
读P2P规范。
1.Device Discovery介绍
P2P Device Discovery虽然也是利用802.11中的Probe Request和Probe Response
帧来搜索周围的P2P设备,但其步骤却比Infrastructure BSS中的无线网络搜索要复杂。举
一个简单的例子,一个P2P Device除了自己要发送Probe Request帧外,还得接收来自其
他设备的Probe Request帧并回复Probe Response帧。而在Infrastructure BSS中,只有
AP会发送Probe Response帧。
为了加快搜索速度,P2P为Device Discovery定义了两个状态和两个阶段。
(1)Device Discovery工作流程
先来看两个状态,分别如下。
·Search State:在该状态中,P2P Device将在2.4GHz的1,6,11频段上分别发送
Probe Request帧。这几个频段称为Social Channels。为了区别非P2P的Probe Request
帧,P2P Device Discovery要求必须在Probe Request帧中包含P2P IE。
·Listen State:在该状态中,P2P Device将随机选择在1,6,11频段中的一个频段
(被选中的频段称为Listen Channel)监听Probe Request帧并回复Probe Response
帧。值得指出的是,Listen Channel一旦选择好后,在整个P2P Discovery阶段就不能更
改。另外,在这个阶段中,P2P Device只处理包含P2P IE信息的Probe Request帧。
再来看两个阶段,分别如下。
·Scan Phase:扫描阶段。这一阶段和前面章节介绍的无线网络扫描一样,P2P
Device会在各个频段上发送Probe Request帧(主动扫描)。P2P Device在这一阶段中不
会处理来自其他设备的Probe Request帧。这一阶段过后,P2P Device将进入下一个阶
段,即Find Phase。
·Find Phase:虽然从中文翻译来看,Scan和Find意思比较接近,但P2P的Find
Phase却和Scan Phase大不相同。在这一阶段中,P2P Device将在Search State和Listen
State之间来回切换。Search State中,P2P Device将发送Probe Request帧,而Listen
State中,它将接收其他设备的Probe Request帧并回复Probe Response帧。
图7-3所示为两个P2P Device的Discovery流程。
·Discovery启动后,Device首先进入Scan Phase。在这一阶段,P2P设备在其支持
的所有频段上都会发送Probe Request帧。
·Scan Phase完成后,Device进入Find Phase。在这一阶段中,Device将在Listen
和Search State中切换。根据前面的介绍,每一个设备的Listen Channel在Discovery开
始前就已确定。例如,图7-3中Device 1的Listen Channel是1,而Device 2的Listen
Channel是6。
图7-3 P2P Device Discovery流程
·在Find Phase中,P2P规范对Device处于Listen State的时间也有所规定,其时间
是100TU的整数倍,倍数值是一个随机数,位于minDiscoverableInterval和
maxDiscoverableInterval之间。这两个值默认为1和3,而厂商可以修改。选择随机倍数
的原因是防止两个Device进入所谓的“Lock-Step怪圈”,即两个Device同时进入Listen
State,等待相同的时间后又同时进入Search State。如此,双方都无法处理对方的Probe
Request信息(Search State中,Device只发送Probe Request)。图7-3中,Device 1
第一次在Listen State中待了2个100TU,而第二次在Listen State中待了1个100TU。
·当Device处于Find Phase中的Search State时,它将在1、6、11频段上发送Probe
Request帧。注意,只有当两个设备处于同一频段时,一方发送的帧才能被对方接收到。
提示 P2P规范对两个状态及两个阶段的描述非常细致,甚至于对每个状态能干什么和
不能干什么都有详细说明。不过,从快速掌握P2P框架的角度来看,这些内容可以省略。
了解Device Discovery的大体工作流程后,下面通过实例来看看P2P使用的Probe
Request和Probe Response帧。
(2)Probe Request帧设置
图7-4所示为Galaxy Note 2在测试P2P功能时发送的Probe Request帧。
图7-4 P2P Probe Request帧实例
图7-4所示的P2P Probe Request帧实例中有三个地方(用黑框标明)值得注意。
①中为SSID,其取值为"DIRECT-“。大家不要小看它,“DIRECT-“就是P2P规范中定
义的P2P W ildcard SSID。
②中为W SC IE。W SC IE中的Device Name属性表明了发送者的设备名。另
外,Probe Request发送者可以利用Primary Device Type属性来搜索指定类型的接收者
(相关内容,读者可参考第6章“Configuration Methods和Primary Device Type属
性”)。
③中为P2P IE。和W SC IE一样,它也属于802.11规范中Vendor自定义的
IE(Element ID取值为221,参考6.3.1节W SC IE和Attribute介绍)。OUI取值为0x50-
6F-9A-09。其中50-6F-9A是W i-Fi Alliance组织的OUI,09代表P2P。P2P IE的组织结
构也是由一个一个的Attribute组成。此处的P2P IE包含P2P Capability和Listen
Channel两个属性,其详情见下文。
图7-4所示的P2P IE中包含了P2P Capability和Listen Channel两个属性。其
中,P2P Capability的示例如图7-5所示。
图7-5中所示的P2P Capability属性表示设备对P2P各种特性支持的情况。它分为
Device Capability Bitmap和Group Capability Bitmap两项考核指标。这两个
Capability Bitmap长度都为1字节,每一位都代表一种P2P特性(这也是它们的名称中都
带有Bitmap一词的原因)。表7-1和表7-2分别展示了这两个Capability中每一位的含义。
图7-5 P2P Capability示例
表7-1中的P2P Client Discoverability对应于下面所述的应用场景。
·P2P Device A已经加入了一个P2P Group 1。在Group 1中,它扮演Client的角
色。
·P2P Device B(不在Group 1中)指定搜索P2P Device A。由于Device A已经扮演
了Client的角色,所以它不会回复Probe Response。不过,Group 1的GO却存储有当前
与它关联的Client信息,即Group 1的GO了解Device A的信息。
·如果Device A支持Client Discoverability,那么Group 1的GO、Device A以及
Device B将借助Device Discoverability Request/ Response帧来获取相关信息。这部分
流程比较复杂,感兴趣的读者不妨阅读参考资料[2]。
注意 通过上面关于P2P Client Discoverability的描述可知,P2P Device
Discovery的内容远比图7-3所示的流程复杂。实际上,图7-3描述的只是P2P Device
Discovery的一种情况。P2P规范中介绍的Device Discovery一共包含有如下几种情况。
·图7-3所示的两个P2P Device搜索,这两个Device都支持P2P并且当前都没有加入
P2P Group。
·一个未加入Group的P2P Device搜索位于某个P2P Group中的GO。
·一个未加入Group的P2P Device搜索位于某个P2P Group中的Client。
·Legacy Client搜索P2P Group中的GO。
·一个P2P Device搜索另一个已经加入某个Infrastructure BSS的Device(通过
Concurrent Operation来同时支持P2P和STA)。
·GO搜索周围的P2P Device和Services。
本章将只分析第一种情况,感兴趣的读者请在学完本章后再研究其他几种情况。
表7-2所示为Group Capability Bitmap各个位的作用。
图7-6 Listen Channel属性
接着来看Listen Channel属性,它代表P2P Device在Listen State时将使用哪个频
段,其内容如图7-6所示。可知包含三个字段。
·Country String:该字段长3字节。其中,前两字节表示国家码(图7-6中的"XX"代
表non-country entity,该值表示当前没有明确的国家)。最后一字节的“04”表示后面
的Operating Class定义需参考资料[5]中的表J-4。
·Operating Class:指明Listen State时使用的频率波段的类别,此处为81。
·Channel Number:指明Listen State使用的频段。
提示 根据参考资料[5]的表J-4(图7-7),Operating Class 81表示频段的起始频率
是2.407GHz,分为13个频段,每个频段的间隔为25MHz。另外,表J-1定义了美国的
Operating Class,表J-2定义了欧洲的Operating Class,表J-3定义了日本的Operating
Class,表J-4定义了其他国家的Operating Class情况。
图7-7 表J-4截图
特别注意 P2P IE还定义了一个名为Operating Channel的属性,其组成结构和
Listen Channel完全一样,但含义却大不相同。Operating Channel表示假设该设备扮演
GO,则由它组建的P2P Group将在哪个频段上工作。并且其频段不限于Social Channels
中指定的那三个频段。
除了包含P2P Capability和Listen Channel两个属性外,Probe Request中的P2P IE
还可以包含其他一些属性。这部分知识见参考资料[6]。
最后,总结P2P规范中对Probe Request帧的要求。
·SSID IE必须设置为P2P W ildcard SSID,即"DIRECT-”。
·必须包含P2P IE。
·802.11 MAC帧头的地址域 ① 中,Destination Address域(Address1)必须为广播
地址(FF:FF:FF:FF:FF:FF)或者为目标设备的P2P Device Address(特别注
意,详情见下文),BSSID域(Address3)必须为广播地址。
特别注意 P2P规范定义了两种类型的地址,一种是P2P Device Address,另外一种
是P2P Interface Address。一个P2P Device在加入P2P Group前,将使用Device
Address开展Device Discovery等工作。对一个P2P Device而言,其P2P Device
Address是唯一的(作用等同于MAC地址)。而当P2P Device加入P2P Group后,它和
Group中其他成员交互时将使用P2P Interface Address。另外,由于一个P2P Device可
同时加入多个P2P Group,所以在每个P2P Group中,该设备必须使用不同的P2P
Interface Address。最后,当一个Group结束后,Device在该Group中使用的P2P
Interface Address也就相应作废了。一般而言,P2P Device Address和P2P Interface
Address不同。以笔者的Galaxy Note 2为例,其P2P Device Address为92:18:7c:
69:88:e2,而P2P Interface Address为92:18:7c:69:08:e2。
接着来看Probe Response帧。
(3)Probe Response帧设置
图7-8所示的P2P Probe Response帧包含W SC IE和P2P IE。在P2P IE中,除了有
P2P Capability属性外,还包含一个名为P2P Device Info的属性。P2P Device Info用于
描述发送设备的一些情况。示例中,该属性的取值如图7-9所示。
图7-8 P2P Probe Response帧示例
图7-9 P2P Device Info示例
仔细观察图7-9,读者会发现,P2P Device Info包含了一些第6章介绍W SC时提到的
属性,例如Primary Device Type、Config Methods等。关于W SC属性,可回顾第6章
W SC IE和Attribute的相关介绍。P2P Device address字段用来描述发送设备的P2P
Device Address。
注意 图7-9中所示的W SC属性中,Primary Device Type和Config Methods这两个
属性没有包含对应的Attribute ID以及Length字段,只包含了Value字段,而Device
Name属性则包含了Attribute ID、Length以及Value字段。
以上介绍了P2P Device Discovery的流程及相关的Probe Request/ Response帧和
P2P IE等内容。值得指出的是,本节是以两个未加入P2P Group的P2P Device互相搜索为
例来介绍Device Discovery流程的,它属于P2P规范Device Discovery各种case中较简单
的一种。
提示 根据笔者阅读P2P规范的心得,P2P Device Discovery实际所包含的内容非常
丰富,而且难度比较大。在此,建议读者在学完本章后再去研读P2P规范。
2.Group Formation介绍
当P2P Device A通过Device Discovery找到周围的一个P2P Device B后,Device A
就可以开展Group Formation流程以准备构造一个P2P Group。Group Formation也包含
两个阶段,分别如下。
·GO Negotiation:在这一阶段中,两个Device要协商好由谁来做GO。
·Provisioning:GO和Client角色确定后,两个Device要借助W SC来交换安全配置
信息。此后,Client就可以利用安全配置信息关联上GO。这个流程和第6章介绍的W SC流
程一样,这部分内容请读者参考第6章。
GO Negotiation过程中P2P设备会利用一种名为P2P Public Action类型的帧交换信
息,所以下面先来认识一下P2P Public Action帧。
提示 除了GO Negotiation外,P2P Invitation、Device Discoverability和
Provision Discovery流程也会用到P2P Public Action帧。
(1)P2P Public Action帧
Action帧是802.11管理帧的一种,其Type和SubType取值可参考3.3.5节帧类型、
From/ To DS介绍。Action帧的作用如其名称中的"Action"所示,发送方利用Action帧携
带一些请求信息,从而使得接收方能对应进行一些处理。
Action帧Frame Body的结构比较简单,仅包含Category和Action Detail两个部
分,Action Detail随Category的不同而变化。常用的Category [7] 如下。
·值为0,表示Spectrum Management,用于Spectrum Measurement。
·值为4,表示Public,P2P规范会使用这种类型的Action帧。
·值为5,表示Radio Management,它和Radio Measurement有关。
·值为127,表示Vendor Specific,它和具体的厂商有关。P2P规范也会使用这种类型
的Action帧。
如上所述,P2P将使用Public Action和Vendor Specific这两种类型的Action帧。本
节先介绍其中的Public Action帧。
802.11中Public Action帧又有多种子类型,而P2P属于Public Action中的Vendor
Specific子类型。P2P使用的Action帧Frame Body的组成结构如表7-3所示。
表7-4所示为OUI Subtype取值,不同的Subtype代表不同的P2P Public Action帧。
下面来看GO Negotiation流程,包含三次P2P Public Action帧交换。
(2)GO Negotiation流程
图7-10为GO Negotiation涉及的三次帧交换流程。
图7-10 GO Negotiation流程
由图7-10可知,GO Negotiation(以后简称GON)流程包括GON Request、GON
Response和GON Confirmation三次帧交换。这三次帧交换并不涉及什么复杂的计算,只
是双方交换一些信息,从而谁来扮演GO。另外,图7-10也列出了这三个帧中可包含的一些
P2P和W SC属性信息。下面将直接分析这三个帧的内容。
1)首先是GON Request,实例如图7-11所示。GON Request帧中P2P IE包含了一
些之前没有提到的属性,下面分别介绍。
首先是GO Intent属性,该属性代表发送设备扮演GO的渴望程度,其内部包含一个名
为GO Intent的字段。该字段长1字节,目前使用的仅是该字节的前八位。
图7-11 GON Request实例
图7-12 GO Intent属性
图7-12所示为图7-11中GO Intent属性的取值情况。
·第0位叫做"Tie Breaker”(意思为决胜因素),Tie Breaker的取值为随机的0或1。
·第1~7位为Intent值,取值为0~15。值越高,代表越想成为GO。15表示该发送设
备必须充当GO的角色。Intent默认值为7。
在GON三次帧交换中,GON Request和GON Response都携带GO Intent,分别代
表了交互双方想成为GO的渴望程度,那么到底谁会成为GO呢?为此,规范制订了一个游
戏规则,如图7-13所示。
图7-13 GO角色扮演规则
由图7-13可知:
·如果Device A的GO Intent小于Device B的GO Intent,则Device B将成为GO。
·一般情况下,Device A和Device B的GO Intent都将使用默认值(值为7)。这种情
况下,Tie Breaker的取值是关键,该字段值为1的Device A将成为GO。由于Tie Breaker
为随机值,所以两个设备的Tie Breaker取值相同的几率非常低。
·“一山不能容二虎”,如果两个设备都想扮演GO(如GO Intent都为15),则GON
失败,谁都成为不了GO。
来看GON Request帧中第二个P2P属性Configuration Timeout,该属性实例如图7-
14所示。Configuration Timeout属性包含两个长度为1字节的字段,分别是GO
Configuration Timeout和Client Configuration Timeout,它们表明Device进入GO或
Client角色的超时时间(这两个字段的取值为10ms的倍数)。
例如,图7-14中的GO Configuration Timeout字段取值为100,对应超时时间为
1000ms,表示Device如果要扮演GO的话,必须在1秒内完成相关准备工作。
图7-14 Configuration Timeout属性
图7-15 Channel List属性
下面来看Channel List属性,它代表发送设备支持的W i-Fi频段信息,图7-15所示为
此属性的实例。
Channel List属性的组成比较简单,包括一个Country String和一个或多个Channel
信息。由于笔者的Galaxy Note 2同时支持2.4GHz(对于Operating Class为81)和
5GHz(对于Operating Class为124)两个频段,所以Channel List包含了两个Channel
信息。
提示 图7-15中Channel List字段中列举的是十六进制的频道号。其中,5GHz段包含
4个频道,分别是149(0x95)、153(0x99)、157(0x9D)和161(0xA1)。关于这些
信息见参考资料[5]。
图7-11中最后一个比较重要的属性就是Intended P2P Interface Address,代表P2P
设备加入Group后将使用的MAC地址。
在Android平台中,当某个设备收到GON Request帧后,将弹出一个如图7-16所示的
提示框以提醒用户。如果用户选择“接受”,则系统将继续后续的工作,否则将终止Group
Formation流程。
图7-16 GON Request接收者提示框
2)接着来看GON Response帧,图7-17为实例。GON Response帧也包含一些新的
P2P属性。首先来看其中的Status属性,该属性代表某一个Action帧的处理结果,值为0表
示处理成功,其他值表示失败。
另一个比较重要的属性是P2P Group ID,其实例如图7-18所示。P2P Group ID用于
唯一标示一个P2P Group,该属性必须包含P2P Device Address以及SSID两个字段。其
中,SSID的格式遵循如下规则。
·开头必须是"DIRECT-xy”,xy为随机的两个大小写字母或数字,例如"ny"。
·规范对规定"DIRECT-xy"之后的内容,Android中会加上设备名,例如图中
的"Android_4aa9"。
图7-17 GON Response帧实例
图7-18 P2P Group ID属性
注意 只有会成为GO的P2P Device在其发送的Group Response帧中才会包含P2P
Group属性。
3)最后看GON Confirmation帧,它是对GON Response的确认。图7-19为GON
Confirmation帧实例。
图7-19 GON Confirmation帧实例
图7-19所示的GON Confirmation帧比较简单。不过,如果发送者将扮演GO角色,其
发送的GON Confirmation帧必须包含P2P Group ID属性。
提示 P2P规范对GON三个帧包含的P2P属性及W SC属性都有明确要求,读者可通过
参考资料[8]来学习相关知识。
GON流程执行完毕后,P2P Device的角色也就随之确定,下一步的工作就是
Provisioning,即交互双方利用W SC来交换安全配置信息,这部分工作在第6章已经详细介
绍过了,此处不赘述。细心的读者可能会发现,P2P Public Action帧中还存在
着"Provision Discovery Request/ Response"类型的帧,它们是干什么用的呢?来看下
文。
(3)Provision Discovery介绍
Provision Discovery也和W SC有关。第6章中曾介绍W SC中的Configuration
Methods属性,它代表了W i-Fi设备所支持的W SC配置方法。W SC定义了一共13种配置方
法。图7-20是该属性的内容。
图7-20 Config Method属性
了解上述信息后,请读者思考一个问题。两个P2P Device如何知道对方使用的是哪种
W SC配置方法呢?显然,如果双方使用不同的W SC配置方法,这个Group就无法建立。
为了解决这个问题,P2P规范定义了Provision Discovery(PD)流程,该流程就是为
了确定交互双方使用的W SC方法。
Provision Discovery包含PD Request和PD Response两次帧交换,其中起到决定作
用的信息是W SC IE的Config Method属性。图7-21所示为PD Request和PD Response
帧实例。
图7-21 PD Request/ Response帧实例
图7-21中的左图为PD Request帧,右图是PD Response帧。根据P2P规范:
·PD Request帧的发送者在W SC IE的Config Method属性中设置想使用的W SC配置
方法,注意,一次只能设置一种W SC配置方法。图7-21的PD Request帧发送者使用了
Push Button方法。
·PD Request帧的接收者如果支持PD Request帧发送者设置的W SC配置方法,则它
将在回复的PD Response帧中对应设置该W SC配置方法。例如图7-21的右图也设置了
Push Button位,表示PD Response帧发送者支持PBC。
·如果PD Request帧的接收者不支持发送者设置的W SC配置方法,它回复的PD
Response帧中,Config Method值为0。这样,PD Request帧发送者将重新选择一种新的
配置方法,然后再次通过PD Request帧向对方发起请求以判断对方是否支持这个新的配置
方法。
简而言之,如果PD Request接收者支持发送者设置的W SC配置方法,则它在PD
Response帧中将设置相同的Config Method属性值,否则设置Config Method值为0。
提示 让笔者颇感纳闷的一件事情是,当PD Request接收者不支持发送者设置的W SC
配置方法时,它为什么不在PD Response帧中设置Config Method为自己所支持的W SC方
法,而仅是通过设置Config Method值为0来简单告诉发送者其设置的配置方法无效呢?感
兴趣的读者不妨对此问题展开讨论。
由于W SC配置需要用户参与,所以PD另外一个作用就是提醒用户执行相应的动作。例
如让用户输入PIN码等。
注意,Provision Discovery不属于Group Formation,它的出现是为了解决如下所
述的一个问题。
为了达到最好的用户体验,P2P规范要求Group Formation(即GON和Provisioning
两个部分)须在15秒内完成。但W SC安全配置往往需要用户参与(例如输入PIN码)。这
些操作比较费时,所以W SC规范(Provisioning遵守W SC规范)对此设置的时间限制是2
分钟。也就是说,光Group Formation中的Provisioning就可能耗费最长2分钟。如何解决
2分钟和15秒之间的矛盾呢?P2P规范提出了Provision Discovery这一方法,其作用如
下。
·PD用于Group Formation之前,以提前邀请用户输入W SC安全配置所需信息(例
如让用户输入PIN码等)。
·PD获取的安全信息(如PIN码)可直接用于后续Group Formation的
Provisioning,从而避免了在Provisioning过程中让用户输入PIN码。
根据规范,Group Formation只包含两个部分。
·GO Negotiation,在此过程中,P2P Device将利用GO Intent来确定谁将扮演
GO,谁将扮演Client。GO Negotiation涉及三次帧GON帧交换。
·GO和Client角色确定后就相当于确定了Infrastructure BSS中的AP和STA,下一
步工作就是双方通过W SC流程交换安全配置信息。在P2P中,该部分称为Provisioning。
Provision Discovery是为了加快Group Formation速度而设计的一种方法,它能在
Group Formation正式开始前通知用户输入与W SC安全配置相关的信息。
① 关于MAC帧头地址域,以及管理帧的地址域情况,参考3.3.5节。
7.2.3 P2P工作流程
P2P规范中附录A [9] 通过定义一个状态机介绍了P2P的整体工作流程,笔者觉得以此作
为本章P2P理论知识的总结是最好不过了。该状态机的状态定义及切换如图7-22所示。
图7-22 P2P状态机
图7-22中,三个黑虚线框分别是Find Phase、Group Formation Procedure和
Operational Phase,这三个Phase描述的是P2P工作流程中的一个阶段,每个阶段可包含
一个或多个状态。例如Group Formation Procedure阶段包含GON、W SC Provisioning
Registrar和W SC Provisioning Enrollee三个状态。
每个状态对应的状态名位于状态框顶部,其字体格式为加粗并带下划线。注意,图中
Search状态包含两个子状态,分别是Search子状态以及Service Discovery子状态。由于
P2P Device并不都支持Service Discovery功能,所以Service Discovery子状态为可选
(operational)状态。
每个状态都有对应的Entry Action、Exit Action和Internal Behavior。其中,EA和
EXA位于状态框图的上半部分,而Internal Behavior位于状态框图的下半部分。状态之间
的切换及切换条件由数字序号及箭头线表示。
下面介绍图7-22中P2P状态机的各个状态以及状态转换条件。对此,我们重点考察每个
状态的EA(Entry Action)、EXA(Exit Action)、Internal Behavior以及
Transition。
一个P2P Device最初的状态是Off,然后将进入Scan状态(括号中的数字对应图7-22
中的数字)。
接着来看Find Phase,它包括Listen和Search两个状态。其中,Listen状态如下。
Find Phase中另外一个状态是Search状态。它包含Search子状态和Service
Discovery子状态。先来看Search子状态。
再来看Service Discovery子状态。
Search状态(包括Search子状态和Service Discovery子状态)的Transition情况如
下。
接着来看Group Formation Procedure,该阶段包含三个状态,首先是GON。
Group Formation Procedure另外两个状态W SC Provisioning Registrar和W SC
Provisioning Enrollee比较简单,请读者根据图7-22自行总结。
最后,来看看Operational Phase,它包含P2P GO和P2P Client两个状态,首先是
P2P GO状态。
再来看P2P Client状态,它没有EA和EXA。
图7-22对掌握P2P整体工作流程有重要意义,读者不妨仔细阅读。从下一节开始,将分
析Android平台中P2P的代码实现。和W SC一样,首先分析的是Java层中的
W ifiP2pSettings以及W ifiP2pService。
7.3 WifiP2pSettings和WifiP2pService介绍
W ifiP2pSettings是Settings应用中负责处理P2P相关UI/ UE逻辑的主要类,与之交互
的则是位于SystemServer进程中的W ifiP2pService。本节先介绍W ifiP2pSettings的工作
流程,然后分析W ifiP2pService。
7.3.1 WifiP2pSettings工作流程
Android平台中,P2P操作特别简单,用户只需执行如下三个步骤。
1)进入W ifiP2pSettings界面。
2)搜索周围的P2P设备。搜索到的设备将显示在W ifiP2pSettings中。
3)用户选择其中的某个设备以发起连接。
下面将根据上面的使用步骤来分析W ifiP2pSettings。首先来看W ifiP2pSettings的
onActivityCreate函数。
1.W ifiP2pSettings创建
W ifiP2pSettings的onActivityCreated函数代码如下所示。
[–>W ifiP2pSettings.java::onActivityCreated]
public void onActivityCreated(Bundle savedInstanceState) {
addPreferencesFromResource(R.xml.wifi_p2p_settings);// 加载界面元素
/*
和第5章介绍的WifiSettings类似,WifiP2pSettings也是通过监听广播的方式来了解系统中
Wi-Fi P2P相关的信息及变化情况。下面这几个广播属于P2P特有的,其作用如下。
WIFI_P2P_STATE_CHANGED_ACTION:用于通知系统中P2P功能的启用情况,如该功能是enable还是disable。
WIFI_P2P_PEERS_CHANGED_ACTION:系统内部将保存搜索到的其他P2P设备信息,如果这些信息有变化,
则系统将发送该广播。接收者需要通过WifiP2pManager的requestPeers函数重新获取这些P2P设备的信息。
WIFI_P2P_CONNECTION_CHANGED_ACTION:用于通知P2P连接情况,该广播可携带WifiP2pInfo
和NetworkInfo两个对象。相关信息可从这两个对象中获取。
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION:用于通知本机P2P设备信息发生了变化。
WIFI_P2P_DISCOVERY_CHANGED_ACTION:用于通知P2P Device Discovery的工作状态,如启动或停止。
WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION:用于通知之persistent group信息发生了变化。
/
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION);
final Activity activity = getActivity();
// 创建WifiP2pManager对象,它将和WifiP2pService交互
mWifiP2pManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
if (mWifiP2pManager != null) {
// 初始化WifiManager并建立和WifiService的联系
mChannel = mWifiP2pManager.initialize(activity, getActivity().getMainLooper(),null);
}

…// 创建UI中按钮对应的onClickListener
mRenameListener = new OnClickListener() {…};

super.onActivityCreated(savedInstanceState);
}
W ifiP2pSettings将在onResume中注册一个广播接收对象以监听上面代码中介绍的广
播事件。这部分代码很简单,请读者自行阅读。
2.W ifiP2pSettings工作流程
(1)W IFI_P2P_STATE_CHANGED_ACTION处理流程
打开W ifiP2pSettings后,首先要等待W IFI_P2P_STATE_CHANGED_ACTION广
播以判断P2P功能是否正常启动。相应的处理函数如下所示。
[–>W ifiP2pSettings.java::onReceive]
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
// 从WIFI_P2P_STATE_CHANGED_ACTION广播中获取相关状态信息以判断P2P功能是否打开
mWifiP2pEnabled = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
WifiP2pManager.WIFI_P2P_STATE_DISABLED) ==
WifiP2pManager.WIFI_P2P_STATE_ENABLED;
handleP2pStateChanged();
}

}
来看handleP2pStateChange函数,代码如下所示。
[–>W ifiP2pSettings.java::handleP2pStateChanged]
private void handleP2pStateChanged() {
updateSearchMenu(false);// 该函数将触发WifiP2pSettings的onCreateOptionsMenu被调用
if (mWifiP2pEnabled) {

/

获取系统当前已经搜索到或者之前保存的P2P Device信息列表。Android为此定义了一个名为
WifiP2pDeviceList的数据类型用来存储这些P2P Device信息。
请读者注意requestPeers函数调用的第二个参数,该参数的类型为PeerListener,它是一个
接口类,而WifiP2pSettings实现了它。WifiP2pDeviceList信息将通过这个接口类的
onPeersAvailable函数返回给requestPeers的调用者。
后文将分析onPeersAvailable函数,此处先略过。
/
mWifiP2pManager.requestPeers(mChannel, WifiP2pSettings.this);
}
}
根据上文的介绍,用户下一步要做的事情就是主动搜索周围的P2P设备。Android原生
代码中的W ifiP2pSettings界面下方有两个按钮,分别是"SEARCH"和"RENAME"。
·“RENAME"用于更改本机的P2P设备名。
·“SEARCH"用于搜索周围的P2P Device。
当P2P功能正常启用后(即上述代码中的mW ifiP2pEnabled为true时),这两个按钮
将被使能。此后,用户就可单击"SEARCH"按钮以搜索周围的P2P设备。该按钮对应的函数
是startSearch,马上来看它。
提示 一些手机厂商对W ifiP2pSettings界面有所更改,但大体流程没有变化。
(2)startSearch函数
startSearch的代码如下所示。
[–>W ifiP2pSettings.java::startSearch]
private void startSearch() {
if (mWifiP2pManager != null && !mWifiP2pSearching) {
// discoverPeers将搜索周围的P2P设备
mWifiP2pManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
…});
}
}
上述代码中,W ifiP2pSettings通过调用W ifiManager的discoverPeers来搜索周围的
设备。
当W PAS完成搜索后,W IFI_P2P_PEERS_CHANGED_ACTION广播将被发送。来
看W ifiP2pSettings中对该广播的处理。
(3)W IFI_P2P_PEERS_CHANGED_ACTION处理流程
广播事件在onReceive函数中被处理,此函数的代码如下所示。
[–>W ifiP2pSettings.java::onReceive]
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();

// 如果搜索到新的P2P Device,则WIFI_P2P_PEERS_CHANGED_ACTION将被发送
} else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
mWifiP2pManager.requestPeers(mChannel, WifiP2pSettings.this);
}…
else if (WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION.equals(action)) {
int discoveryState = intent.getIntExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE,
WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED);
if (discoveryState == WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED)
updateSearchMenu(true);// 更新“SEARCH”按钮显示的名称
else updateSearchMenu(false);
}…
}
};
注意,startSearch还将触发系统发送
W IFI_P2P_DISCOVERY_CHANGED_ACTION广播,W ifiP2pSettings将根据该广播携
带的信息来更新"SEARCH"按钮的界面,其规则是:如果P2P Discovery启动成功(即状态
变为W IFI_P2P_DISCOVERY_STARTED),则"SEARCH"按钮名显示
为"Searching…”,否则该按钮名显示为"Search For Devices”。
当系统搜索到新的P2P Device后,W IFI_P2P_PEERS_CHANGED_ACTION广播将
被发送,而W ifiP2pSettings对于该广播的处理就是调用W ifiP2pManager的
requestPeers来获取系统保存的P2P Device信息列表。
前文代码中曾介绍过requestPeers,系统中所有的P2P设备信息将通过PeerListener
接口类的onPeersAvailable函数返回给W ifiP2pSettings。直接来看该函数,代码如下所
示。
[–>W ifiP2pSettings.java::onPeersAvailable]
public void onPeersAvailable(WifiP2pDeviceList peers) {
// 系统中所有的P2P设备信息都保存在这个类型为WifiP2pDeviceList的peers对象中
mPeersGroup.removeAll();// mPeersGroup类型为PerferenceGroup,属于UI相关的类
mPeers = peers;
mConnectedDevices = 0;
for (WifiP2pDevice peer: peers.getDeviceList()) {
// WifiP2pPeer是Perference的子类,它和UI相关
mPeersGroup.addPreference(new WifiP2pPeer(getActivity(), peer));
if (peer.status == WifiP2pDevice.CONNECTED) mConnectedDevices++;
}
}
在onPeersAvailable函数中,W ifiP2pDeviceList中保存的每一个W ifiP2pDevice信
息将作为一个Preference项添加到mPeersGroup中并显示在UI界面上。
接下来,用户就可在界面中选择某个P2P Device并与之连接。这个步骤由
onPreferenceTreeClick函数来完成。
提示 上述代码中提到的W ifiP2pDevice等数据结构将放到下文再介绍。
(4)onPreferenceTreeClick函数
onPreferenceTreeClick的代码如下所示。
[–>W ifiP2pSettings.java::onPreferenceTreeClick]
public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
if (preference instanceof WifiP2pPeer) {
mSelectedWifiPeer = (WifiP2pPeer) preference;// 获取用户指定的那一个WifiP2pPeer项
if (mSelectedWifiPeer.device.status == WifiP2pDevice.CONNECTED)
showDialog(DIALOG_DISCONNECT);// 如果已经和该Device连接,则判断是否需要与之断开连接
else if (mSelectedWifiPeer.device.status == WifiP2pDevice.INVITED)
showDialog(DIALOG_CANCEL_CONNECT);
else {// 向对端P2P设备发起连接
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = mSelectedWifiPeer.device.deviceAddress;
// 判断系统是否强制使用了某种WSC配置方法
int forceWps = SystemProperties.getInt(“wifidirect.wps”, -1);
if (forceWps != -1) config.wps.setup = forceWps;
else {
// 获取对端P2P Device支持的WSC配置方法,优先考虑PBC
if (mSelectedWifiPeer.device.wpsPbcSupported())
config.wps.setup = WpsInfo.PBC;
else if (mSelectedWifiPeer.device.wpsKeypadSupported()) {
config.wps.setup = WpsInfo.KEYPAD;
else config.wps.setup = WpsInfo.DISPLAY;
}
// 通过WifiP2pManager的connect函数向对端P2P Device发起连接。注意,目标设备
// 信息保存在config对象中
mWifiP2pManager.connect(mChannel, config,
new WifiP2pManager.ActionListener(){…});
}
} …
}
(5)W IFI_P2P_CONNECTION_CHANGED_ACTION处理流程
当系统完成和指定P2P Device的连接后,W ifiP2pSettings将收到
W IFI_P2P_CONNECTION_CHANGED_ACTION广播,其对应的代码逻辑如下所示。
[–>W ifiP2pSettings.java::onReceive]
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();

} else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(
WifiP2pManager.EXTRA_NETWORK_INFO);
if (mWifiP2pManager != null){
// requestGroupInfo第二个参数的类型是GroupInfoListener,用于返回Group信息
mWifiP2pManager.requestGroupInfo(mChannel, WifiP2pSettings.this);
}
if (networkInfo.isConnected()){
if (DBG) Log.d(TAG, “Connected”);
} else startSearch(); // 如果没有加入某个P2P Group,则重新发起设备扫描
}…
}
当设备加入一个Group后,requestGroupInfo将通过其第二个参数设置的回调函数以
返回此Group的信息,这个回调函数由GroupInfoListener接口类定
义,W ifiP2pSettings只需实现该接口类的onGroupInfoAvailable,相关代码如下所示。
[–>W ifiP2pSettings.java::onGroupInfoAvailable]
public void onGroupInfoAvailable(WifiP2pGroup group) {
mConnectedGroup = group; // 保存当前所加入的Group的信息
updateDevicePref(); // 更新WifiP2pPeer的界面
}
以上是W ifiP2pSettings的大体工作流程,读者只要把握几个重要广播的处理流程即可
掌握Settings应用中和W i-Fi P2P相关的知识。
3.W ifiP2pSettings总结
上述代码中有两个比较重要的数据结构,W ifiP2pDevice和W ifiP2pGroup,成员比较
简单,此处不赘述。它们的类图如图7-23所示。
图7-23 W ifiP2pDevice和W ifiP2pGroup类图
通过上面几节的代码可知,W ifiP2pSettings将借助W ifiP2pManager和系统中的
W ifiP2pService交互。W ifiP2pSettings主要使用W ifiP2pManager的几个重要函数。
·initialize:初始化W ifiP2pManager,其内部将和W ifiP2pService通过
AsyncChannel建立交互关系。
·discoverPeers:触发W ifiP2pService发起P2P Device扫描工作。
·requestPeers:获取系统中保存的P2P Device信息。
·connect:和指定P2P Device发起连接,也就是相互协商以创建或加入一个P2P网
络,即一个Group。
·requestGroupInfo:获取当前连接上的Group信息。
下面来看W ifiP2pService,它是Android系统中P2P功能的Java层核心模块。
7.3.2 WifiP2pService工作流程
W ifiP2pService和第5章介绍的W ifiService一样,都属于Android系统中负责处理
W i-Fi相关工作的核心模块。其中,W ifiService处理和W LAN网络连接相关的工作,而
W ifiP2pService则专门负责处理和W i-Fi P2P相关的工作。图7-24所示为
W ifiP2pService家族类图。
图7-24 W ifiP2pService类图
图7-24所示的W ifiP2pService家族类图和图5-1所示的W ifiService家族类图类似,此
处就不详细讨论了。直接来看W ifiP2pService的代码,首先是它的构造函数,如下所示。
[–>W ifiP2pService.java::W ifiP2pService]
public WifiP2pService(Context context) {
mContext = context;
mInterface = “p2p0”;// P2P使用的虚拟网络接口设备名为“p2p0”
mActivityMgr = (ActivityManager)context.getSystemService(Activity.ACTIVITY_SERVICE);
mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0, NETWORKTYPE, “”);
// 判断系统是否支持WiFi-Direct功能
mP2pSupported = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WIFI_DIRECT);
// 获取PrimaryDeviceType,默认值是“10-0050F204-5”。结合图6-15可知
// “10”是Category ID,代表Telephone
// “0050F204”是WFA的OUI,最后一个“5”是Sub Category ID,在Telephone大类里边,它代表
// 支持Dual Mode的Smartphone(规范中定义为Smart phone-Dual mode)
mThisDevice.primaryDeviceType = mContext.getResources().getString(
com.android.internal.R.string.config_wifi_p2p_device_type);
// WifiP2pService主要工作也是由状态机来完成的,即下面的这个P2pStateMachine
mP2pStateMachine = new P2pStateMachine(TAG, mP2pSupported);
mP2pStateMachine.start();// 启动P2P状态机
}
P2pStateMachine是W ifiP2pService定义的内部类,它比第5章介绍的
W ifiStateMachine简单,其构造函数如下所示。
[–>W ifiP2pService.java::P2pStateMachine构造函数]
P2pStateMachine(String name, boolean p2pSupported) {
super(name);
addState(mDefaultState);// 为状态机添加状态,一共15个状态
addState(mP2pNotSupportedState, mDefaultState);

if (p2pSupported) setInitialState(mP2pDisabledState);// 初始状态为P2pDisabledState
else setInitialState(mP2pNotSupportedState);
}
图7-25描述了P2pStateMachine中定义的各个状态及层级关系。
图7-25 P2pStateMachine状态机
P2pStateMachine的初始状态是P2pDisabledState,它和父状态DefaultState的
Entry Action都没有执行什么有意义的事情,故此处略去对二者EA的介绍。
P2pStateMachine是W ifiP2pService的核心,我们马上来介绍它的工作流程。
1.CMD_ENABLE_P2P处理流程
P2pStateMachine虽然属于W ifiP2pService,但它也受W ifiStateMachine的影响。
通过5.2.3节“W ifiStateMachine构造函数分析之二”中对W ifiStateMachine
InitialState EA的介绍,会发现W ifiStateMachine将创建一个名为mW ifiP2pChannel的
AsyncChannel对象用于向P2pStateMachine发送消息。
在Android平台中,如果用户打开W i-Fi功能,P2pStateMachine就会收到第一个消
息CMD_ENABLE_P2P。该消息是W ifiStateMachine进入DriverStartedState后,在其
EA中借助mW ifiP2pChannel向P2pStateMachine发送的(可参考5.3.2节
SUP_CONNECTION_EVENT处理流程分析) ① 。
P2pStateMachine此时处于P2pDisabledState,它对CMD_ENABLE_P2P消息的处
理逻辑如下所示。
[–>W ifiP2pService.java::P2pDisabledState:enter]
class P2pDisabledState extends State {
public boolean processMessage(Message message) {
switch (message.what) {
case WifiStateMachine.CMD_ENABLE_P2P:
try {
mNwService.setInterfaceUp(mInterface);
} …
// 启动WifiMonitor,它将通过wpa_ctl连接上wpa_supplicant。关于wpa_supplicant的启动
// 读者可参考5.2.3节“WifiNative介绍”
mWifiMonitor.startMonitoring();
// 转入P2pEnablingState,其EA未作有意义的事情,读者可自行阅读它
transitionTo(mP2pEnablingState);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
处理完CMD_ENABLE_P2P消息后,P2pStateMachine将创建一个W ifiMonitor用于
接收来自wpa_supplicant的消息,同时状态机将转入P2pEnablingState。
W ifiMonitor连接wpa_supplicant之后,W ifiMonitor会发送一个
SUP_CONNECTION_EVENT给P2pStateMachine。该消息将由P2pEnablingState处
理,马上来看相关的处理流程。
2.SUP_CONNECTION_EVENT处理流程
代码如下。
[–>W ifiP2pService.java::P2pEnablingState:processMessage]
class P2pEnablingState extends State {

public boolean processMessage(Message message) {
switch (message.what) {
case WifiMonitor.SUP_CONNECTION_EVENT:
transitionTo(mInactiveState);// 转入InactiveState
break;

}
return NOT_HANDLED
}
}
根据5.2.1节HSM的知识,当状态机转入InactiveState后,首先执行的是其父状态
P2pEnabledState的EA,然后才是InactiveState自己的EA。由于InactiveState的EA仅
打印了一句日志输出,故此处仅介绍P2pEnabledState的EA,相关代码如下所示。
[–>W ifiP2pService.java::P2pEnabledState:enter]
class P2pEnabledState extends State {
public void enter() {
// 发送WIFI_P2P_STATE_CHANGED_ACTION广播,并设置EXTRA_WIFI_STATE状态为
// WIFI_P2P_STATE_ENABLED
sendP2pStateChangedBroadcast(true);
mNetworkInfo.setIsAvailable(true);
/

发送WIFI_P2P_CONNECTION_CHANGED_ACTION广播,它将携带WifiP2pInfo和NetworkInfo信息。
注意,下面这个函数还会向WifiStateMachine发送P2P_CONNECTION_CHANGED消息。读者不妨
自行研究WifiStateMachine对P2P_CONNECTION_CHANGED消息的处理流程。
/
sendP2pConnectionChangedBroadcast();
initializeP2pSettings();// 初始化P2P的一些设置,详情见下文
}
我们重点关注上面代码中的initializeP2pSettings函数,其代码如下所示。
[–>W ifiP2pSettings.java::initializeP2pSettings]
private void initializeP2pSettings() {
/

发送“SET persistent_reconnect 1”给WPAS,该命令对应如下一种应用场景。
当发现一个Persistent Group时,如果 persistent_reconnect为1,则可利用之前保存的配置信息自动重连,
重新连接时无需用户参与。如果persistent_reconnect为0,则需要提醒用户,让用户来决定是否加入此
persistent group。
/
mWifiNative.setPersistentReconnect(true);
/

获取P2P Device Name,先从数据库中查询“wifi_p2p_device_name”字段的值,如果数据库中没有设置
该字段,则取数据库中“android_id”字段值的前4个字符并在其前面加上“Android_”字符串以
组成P2P Device Name。以Galaxy Note 2为例,数据库文件是/data/data/com.android.providers.
settings/database/settings.db,所查询的表名为secure,wifi_p2p_device_name字段取值为
“Android_4aa9”,android_id字段取值为“4aa9213016889423”。
/
mThisDevice.deviceName = getPersistedDeviceName();// mThisDevice指向一个WifiP2pDevice对象
// 将P2P DeviceName保存到WPAS中
mWifiNative.setDeviceName(mThisDevice.deviceName);
// 设置P2P网络SSID的后缀。如果本设备能扮演GO,则它构建的Group对应的SSID后缀就是此处设置的后缀名
mWifiNative.setP2pSsidPostfix(“-” + mThisDevice.deviceName);
// 设置Primary DeviceType
mWifiNative.setDeviceType(mThisDevice.primaryDeviceType);
// 设置支持的WSC配置方法
mWifiNative.setConfigMethods(“virtual_push_button physical_display keypad”);
// 设置STA连接的优先级高于P2P连接
mWifiNative.setConcurrencyPriority(“sta”);
// 从WPAS中获取P2P Device Address
mThisDevice.deviceAddress = mWifiNative.p2pGetDeviceAddress();
// 更新自己的状态,并发送WIFI_P2P_THIS_DEVICE_CHANGED_ACTION消息
updateThisDevice(WifiP2pDevice.AVAILABLE);
mClientInfoList.clear();
// 清空WPAS中保存peer P2P Device和Service信息
mWifiNative.p2pFlush(); mWifiNative.p2pServiceFlush();
mServiceTransactionId = 0; mServiceDiscReqId = null;
/

WPAS中会保存persistent Group信息,而P2pStateMachine也会保存一些信息,下面这个函数将根据
WPAS中的信息来更新P2pStateMachine中保存的Group信息。P2pStateMachine通过一个名为mGroups
的成员变量(类型为WifiP2pGroupList)来保存所有的Group信息。
/
updatePersistentNetworks(RELOAD);
}
至此,P2pStateMachine就算初始化完毕,接下来的工作就是处理用户发起的操作。
首先来看W ifiP2pSettings中W ifiP2pManager的discoverPeers函数,它将发送
DISCOVER_PEERS消息给P2pStateMachine。
3.DISCOVER_PEERS处理流程
P2pStateMachine当前处于InactiveState,不过DISCOVER_PEERS消息却是由其
父状态P2pEnabledState来处理的,相关代码如下所示。
[–>W ifiP2pService.java::P2pEnabledState:processMessage]
class P2pEnabledState extends State{
public boolean processMessage(Message message) {
switch (message.what) {
case WifiP2pManager.DISCOVER_PEERS:
clearSupplicantServiceRequest();// 先取消Service Discovery请求
// 发送“P2P_FIND 超时时间”给WPAS,DISCOVER_TIMEOUT_S值为120秒
if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED);
// 发送WIFI_P2P_DISCOVERY_CHANGED_ACTION广播以通知P2P Device Discovery已启动
sendP2pDiscoveryChangedBroadcast(true);
}…
break;
}
}
}
当W PAS搜索到周围的P2P Device后,将发送以下格式的消息给W ifiMonitor。
P2P-DEVICE-FOUND fa:7b:7a:42:02:13 p2p_dev_addr=fa:7b:7a:42:02:13 pri_dev_type=1-0050F204-1 name='p2p-
TEST1’config_methods=0x188 dev_capab=0x27 group_capab=0x0
W ifiMonitor将根据这些信息构建一个W ifiP2pDevice对象,然后发送
P2P_DEVICE_FOUND_EVENT给P2pStateMachine。
4.P2P_DEVICE_FOUND_EVENT处理流程
同样,P2P_DEVICE_FOUND_EVENT也由InactiveState的父状态
P2pEnabledState来处理,相关代码如下所示。
[–>W ifiP2pService.java::P2pEnabledState:processMessage]
class P2pEnabledState extends State{
public boolean processMessage(Message message) {
switch (message.what) {

case WifiMonitor.P2P_DEVICE_FOUND_EVENT:
// WifiMonitor根据WPAS反馈的信息构建一个WifiP2pDevice对象
WifiP2pDevice device = (WifiP2pDevice) message.obj;
// 如果搜索到的这个P2P Device是自己(根据Device Address来判断),则不处理它
if (mThisDevice.deviceAddress.equals(device.deviceAddress)) break;
/

mPeers指向一个WifiP2pDeviceList对象。如果之前已存储了此Device的信息,
更新这些信息,否则将添加一个新的WifiP2pDevice对象。
/
mPeers.update(device);
sendP2pPeersChangedBroadcast();// 发送WIFI_P2P_PEERS_CHANGED_ACTION广播
break;
}…
}
}
W ifiP2pSettings收到W IFI_P2P_PEERS_CHANGED_ACTION广播后,将通过
W ifiP2pManager的requestPeers来获得当前搜索到的P2P Device信息(即mPeers的内
容)。这部分处理逻辑非常简单,请读者自行阅读相关代码。
现在,用户将选择一个P2P Device然后通过W ifiP2pManager的connect函数向其发
起连接。来看相关代码。
5.CONNECT处理流程
W ifiP2pManager的connect函数将发送CONNECT消息给P2pStateMachine,该消
息由InactiveState状态自己来处理,代码如下所示。
[–>W ifiP2pSettings.java::InactiveState:processMessage]
class InactiveState extends State {

public boolean processMessage(Message message) {
switch (message.what) {
case WifiP2pManager.CONNECT:
/

WifiP2pSettings将设置一个WifiP2pConfig对象以告诉P2pStateMachine该连接
哪一个P2P Device(参考7.3.1节onPreferenceTreeClick介绍)
/
WifiP2pConfig config = (WifiP2pConfig) message.obj;
mAutonomousGroup = false;
// 获取该P2P Device的Group Capability信息
int gc = mWifiNative.getGroupCapability(config.deviceAddress);
mPeers.updateGroupCapability(config.deviceAddress, gc);
// 关键函数connect,见下文介绍
int connectRet = connect(config, TRY_REINVOCATION);
// TRY_REINVOCATION值为true

mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
sendP2pPeersChangedBroadcast();
replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED);
// 根据connectRet的值进行状态切换选择
if (connectRet == NEEDS_PROVISION_REQ) {
transitionTo(mProvisionDiscoveryState);// 转入ProvisionDiscoveryState
break;
}
transitionTo(mGroupNegotiationState);// 或者转入GroupNegotiationState
break;

}
return HANDLED
}
上述代码中有一个关键函数,即connect,其代码如下所示。
[–>W ifiP2pService.java::connect]
private int connect(WifiP2pConfig config, boolean tryInvocation) {

// 当前还没有保存的对端P2P Device配置信息(对应的数据类型为WifiP2pConfig)
// 所以isResp为false
boolean isResp = (mSavedPeerConfig != null &&
config.deviceAddress.equals(mSavedPeerConfig.deviceAddress));
mSavedPeerConfig = config;// 保存传入的WifiP2pConfig信息
WifiP2pDevice dev = mPeers.get(config.deviceAddress);

// 判断对端设备是否为GO。由于还没有开展GON,所以join为false
boolean join = dev.isGroupOwner();
String ssid = mWifiNative.p2pGetSsid(dev.deviceAddress);
// 如果join为true,但对端设备不能再添加新的P2P Device,则join被设置为false
if (join && dev.isGroupLimit()) join = false;
else if (join) {// mGroups保存搜索到的GO信息,当前还没有GO,所以netId为-1
int netId = mGroups.getNetworkId(dev.deviceAddress, ssid);
if (netId >= 0) {// 这种情况对应于加入一个当前已经存在的P2P Group
if (!mWifiNative.p2pGroupAdd(netId)) return CONNECT_FAILURE;
return CONNECT_SUCCESS;
}
}
if (!join && dev.isDeviceLimit()) return CONNECT_FAILURE;
// tryInvocation为true。P2P Device一般都支持Invitation
// 下面这个if代码段处理Persistent Group的情况
if (!join && tryInvocation && dev.isInvitationCapable()) {
int netId = WifiP2pGroup.PERSISTENT_NET_ID;// PERSISTENT_NET_ID值为-2
if (config.netId >= 0) {
if (config.deviceAddress.equals(mGroups.getOwnerAddr(config.netId)))
netId = config.netId;
} else netId = mGroups.getNetworkId(dev.deviceAddress);
if (netId < 0) netId = getNetworkIdFromClientList(dev.deviceAddress);
if (netId >= 0) {// 通过Invitation Request重新启动一个Persistent Group
if (mWifiNative.p2pReinvoke(netId, dev.deviceAddress)) {
mSavedPeerConfig.netId = netId;
return CONNECT_SUCCESS;
} else updatePersistentNetworks(RELOAD);
}
}
mWifiNative.p2pStopFind();
if (!isResp) return NEEDS_PROVISION_REQ;// 就本例而言,connect返回NEEDS_PROVISION_REQ
p2pConnectWithPinDisplay(config);// 发起P2P连接,即启动Group Formation流程
return CONNECT_SUCCESS;
}
就本例而言,connect将返回NEEDS_PROVISON_REQ,所以P2pStateMachine将
转入ProvisionDiscoveryState,马上来看它的EA。
[–>W ifiP2pService.java::ProvisionDiscoveryState:enter]
class ProvisionDiscoveryState extends State {
public void enter() {
// 触发本机设备向对端设备发送Provision Discovery Request帧
mWifiNative.p2pProvisionDiscovery(mSavedPeerConfig);
}
注意,由于W SC配置方法为PBC,所以对端设备的P2pStateMachine将收到一个
P2P_PROV_DISC_PBC_REQ_EVENT消息。当对端设备处理完毕后,将收到一个
P2P_PROV_DISC_PBC_RSP_EVENT消息。马上来看
P2P_PROV_DISC_PBC_RSP_EVENT消息的处理流程。
6.P2P_PROV_DISC_PBC_RSP_EVENT处理流程
P2pStateMachine当前处于ProvisionDiscoveryState,相关处理逻辑如下所示。
[–>W ifiP2pService.java::ProvisionDiscoveryState:processMessage]
public boolean processMessage(Message message) {
WifiP2pProvDiscEvent provDisc;
WifiP2pDevice device;
switch (message.what) {
case WifiMonitor.P2P_PROV_DISC_PBC_RSP_EVENT:
provDisc = (WifiP2pProvDiscEvent) message.obj;
device = provDisc.device;
if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) break;
if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) {
/

下面这个函数将调用WifiNative的p2pConnect函数,此函数将触发WPAS发送
GON Request帧。接收端设备收到该帧后,将弹出图7-16所示的提示框以提醒用户。
/
p2pConnectWithPinDisplay(mSavedPeerConfig);
// 转入GroupNegotiationState,其EA比较简单,请读者自行阅读
transitionTo(mGroupNegotiationState);
}
break;

}
}
上述代码中,P2pStateMachine通过p2pConnectW ithPinDisplay向对端发起Group
Negotiation Request请求。接下来的工作就由W PAS来处理。当Group Formation结束
后,P2pStateMachine将收到一个P2P_GROUP_STARTED_EVENT消息以通知Group建
立完毕,该消息的处理流程如下节所述。
7.P2P_GROUP_STARTED_EVENT处理流程
P2P_GROUP_STARTED_EVENT消息由GroupNegotiationState处理,相关代码如
下所示。
[–>W ifiP2pService.java::GroupNegotiationState:processMessage]
class GroupNegotiationState extends State {

public boolean processMessage(Message message) {
switch (message.what) {
case WifiMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT:
case WifiMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT:
break;// 不处理Group Negotiation成功的消息
case WifiMonitor.P2P_GROUP_STARTED_EVENT:// 只处理Group Started消息
mGroup = (WifiP2pGroup) message.obj;
if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) {
updatePersistentNetworks(NO_RELOAD);
String devAddr = mGroup.getOwner().deviceAddress;
mGroup.setNetworkId(mGroups.getNetworkId(devAddr,
mGroup.getNetworkName()));
}
if (mGroup.isGroupOwner()) {// 如果本机P2P设备是GO,则启动DhcpServer
// 假设本机P2P设备扮演GO,请读者自行阅读startDhcpServer函数
startDhcpServer(mGroup.getInterface());
} else {
/

如果对端设备是GO,则启动DhcpStateMachine用于获取一个IP地址,这部分流程和
5.3.2节NETWORK_CONNECTION_EVENT消息处理流程分析的
ObtainingIpState工作流程类似。
/
mWifiNative.setP2pGroupIdle(mGroup.getInterface(), GROUP_IDLE_TIME_S);
mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(mContext,
P2pStateMachine.this, mGroup.getInterface());
mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
WifiP2pDevice groupOwner = mGroup.getOwner();
groupOwner.update(mPeers.get(groupOwner.deviceAddress));
mPeers.updateStatus(groupOwner.deviceAddress,WifiP2pDevice.CONNECTED);
sendP2pPeersChangedBroadcast();
}
mSavedPeerConfig = null;
transitionTo(mGroupCreatedState);// 转入GroupCreatedState
break;

}
}
P2pStateMachine将转入GroupCreatedState,其EA代码如下所示。
[–>W ifiP2pService.java::GroupCreatedState:enter]
class GroupCreatedState extends State {
public void enter() {
mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
updateThisDevice(WifiP2pDevice.CONNECTED);// 连接成功
if (mGroup.isGroupOwner()) {
/

SERVER_ADDRESS为“192.168.49.1”,该地址也被设置到Dhcp Server中。
另外,P2pStateMachine有一个名为mWifiP2pInfo的成员变量,其类型为WifiP2pInfo,
下面这个函数也将GO的IP地址保存到mWifiP2pInfo中。
*/
setWifiP2pInfoOnGroupFormation(SERVER_ADDRESS);
sendP2pConnectionChangedBroadcast();// 发送WIFI_P2P_CONNECTION_CHANGED_ACTION广播
}
}

}
8.AP_STA_CONNECTED_EVENT处理流程
当对端P2P设备成功关联到本机后,W ifiMonitor又将发送一个名为
AP_STA_CONNECTED_EVENT的消息,该消息的处理逻辑如下所示。
[–>W ifiP2pService.java::GroupCreatedState:enter]
public boolean processMessage(Message message) {
switch (message.what) {
case WifiMonitor.AP_STA_CONNECTED_EVENT:// 该消息表示一个P2P Client关联上本机GO
WifiP2pDevice device = (WifiP2pDevice) message.obj;
String deviceAddress = device.deviceAddress;
if (deviceAddress != null) {

mGroup.addClient(deviceAddress);// 添加一个P2P Client
mPeers.updateStatus(deviceAddress, WifiP2pDevice.CONNECTED);
sendP2pPeersChangedBroadcast();
}

break;

}
至此,一个P2P Device(扮演Client)就成功关联上本机的P2P Device(扮演
GO)。
9.W ifiP2pService总结
回顾上文介绍的W ifiP2pService工作流程,可知P2pStateMachine初始状态为
P2pDisabledState,然后:
1)P2pStateMachine将接收到的第一条消息,它是来自W ifiStateMachine的
CMD_ENABLE_P2P。在该消息的处理逻辑中,P2pStateMachine将创建一个
W ifiMonitor对象以和wpa_supplicant进程交互。最后,P2pStateMachine转入
P2pEnablingState。
2)在P2pEnablingState中,P2pStateMachine将处理SUP_CONNECT_EVENT消
息,它代表W ifiMonitor成功连接上了wpa_supplicant。该消息处理完毕
后,P2pStateMachine将转入InactiveState。
3)InactiveState的父状态是P2pEnabledState,P2pEnabledState的EA将初始化
P2P设置,这部分代码逻辑在initializeP2pSettings函数中。另外,W ifiP2pSettings将收
到一些P2P广播,此时P2P功能正常启动。
4)用户在界面中进行操作以搜索周围的设备,这使得P2pStateMachine将收到
DISCVOER_PEERS消息。它在P2pEnabledState中被处理,wpas_supplicant将发起
P2P Device Discovery流程以搜索周围的P2P设备。
5)一旦有P2P设备被搜索到,P2pStateMachine将接收到一条
P2P_DEVICE_FOUND_EVENT消息。该消息依然由P2pEnabledState来处理。同
时,W ifiP2pSettings也会相应收到信息以更新UI。
6)当用户在W ifiP2pSettings界面中选择连接某个P2P Device后,W ifiP2pSettings
将发送CONNECT消息给P2pStateMachine。该消息由InactiveState来处理。大部分情
况下(除了Persistent Group或者对端设备是GO的情况下),P2pStateMachine将转入
ProvisionDiscoveryState。
7)ProvisionDiscoveryState中,P2pStateMachine将通知W PAS以开展
Provisioning Discovery流程。一切顺利的话,P2pStateMachine将接收到
P2P_PROV_DISC_PBC_RSP_EVENT消息。在该消息的处理过程中,P2pStateMachine
将通过p2pConnectW ithPinDisplay函数通知W PAS和对端设备启动Group Formation流
程。此后,P2pStateMachine转入GroupNegotiationState。
8)Group Formation完成,一个Group也就创建成功,P2pStateMachine将收到
P2P_GROUP_STARTED_EVENT消息。该消息由GroupNegotiationState处理。如果本
机扮演GO的话,它将启动一个Dhcp服务器,也就是第2章提到的dnsmasq(详情请参考
2.3.8节“背景知识介绍”)。
9)当对端P2P Client(Group建立后,角色也就确定了)关联上本机的GO
后,AP_STA_CONNECTED_EVENT消息将被发送给P2pStateMachine处理。
如果仔细阅读W ifiP2pService代码,会发现本节介绍的工作流程是W ifiP2pService中
最简单的一条了。经过笔者实际测试,W ifiP2pService有一个工作场景的处理流程比较复
杂,即如果用户在对端设备发起connect操作,则本机的处理相对要复杂一些。这部分流程
和wpa_suppliant的处理也有关系,所以请读者在学完本章的基础上再自行研究它。
现在,让我们抖擞精神来分析P2P真正的主角wpa_supplicant。
① 注意,Android原生代码中,P2P和STA功能是能同时启用的,但有一些手机不支持
concurrent operation,所以这些手机需要修改W i-Fi相关的代码。
7.4 wpa_supplicant中的P2P
在5.2.3节曾介绍,wpa_supplicant进程由W ifiStateMachine启动。在Android官方
代码中,虽然Java层有W ifiService和W ifiP2pService两个几乎完全不同的W i-Fi服务,
但二者都只和Native层的唯一一个wpa_supplicant进程交互。简单点说,Android原生代
码中,一个wpa_supplicant进程将同时支持W ifiService和W ifiP2pService。
上述这种设计方法使得wpa_supplicant负担较重,所以,一些手机厂商会为
W ifiService和W ifiP2pService各创建一个wpa_supplicant进程,使得它们能各司其职而
互不干扰。以笔者的Galaxy Note 2为例,W ifiService将和wpa_supplicant进程交互,而
W ifiP2pService将和一个名为p2p_supplicant(经过笔者测试,p2p_supplicant实际上就
是wpa_supplicant,只不过名字不同而已)的进程交互。
图7-26所示为Galaxy Note 2 init配置文件中关于p2p_supplicant服务的示意图。
图7-26 Galaxy Note 2中p2p_supplicant服务配置项
由图7-26可知,init配置文件定义了一个名为p2p_supplicant的服务,该服务启动的
进程为p2p_supplicant。
p2p_supplicant使用的配置文件名为/ data/ misc/ wifi/ p2p_supplicant.conf,其内容
如图7-27所示。
提示 关于init配置文件中wpa_supplicant服务的说明,请参考4.3节
wpa_supplicant初始化流程分析。
图7-27中,p2p_supplicant对应的ctrl_iface路径为/ data/ misc/ wifi/ sockets。所
以,如果要使用wpa_cli和p2p_supplicant交互,必须指定正确的ctrl_iface路径。图7-28
所示为笔者用wpa_cli测试p2p_supplicant时的截图。
图7-27 p2p_supplicant.conf内容
图7-28 wpa_cli和p2p_supplicant交互
下面来分析wpa_supplicant中和P2P相关的代码。
注意 以Galaxy Note 2为例,p2p_supplicant就是wpa_supplicant,只是编译时打
开了P2P相关的选项。下面的分析将以wpa_supplicant中和P2P相关的代码及工作流程为
主。
7.4.1 P2P模块初始化
首先来看W PAS中P2P相关模块的初始化。该初始化工作在4.3.4
节“wpa_supplicant_init_iface分析之五”曾提到过,其对应的函数wpas_p2p_init如
下。
[p2p_supplicant.c::wpas_p2p_init]
int wpas_p2p_init(struct wpa_global *global, struct wpa_supplicant wpa_s)
{
struct p2p_config p2p; // p2p变量指向一个p2p_config对象,代表P2P模块的配置信息
unsigned int r; int i;
// ①WPA_DRIVER_FLAGS_P2P_CAPABLE代表Wifi驱动对P2P支持的能力,详情见下文解释
if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_P2P_CAPABLE)) return 0;
if (global->p2p) return 0;
// 如果wifi driver能完成P2P功能,就不用劳驾WPAS了
if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_P2P_MGMT) {…}
// ②初始化并设置p2p_config对象
os_memset(&p2p, 0, sizeof(p2p));
p2p.msg_ctx = wpa_s; p2p.cb_ctx = wpa_s;
p2p.p2p_scan = wpas_p2p_scan; // P2P对应的扫描函数
…// 设置一些回调函数
p2p.get_noa = wpas_get_noa; p2p.go_connected = wpas_go_connected;
// 设置P2P Device address。
os_memcpy(wpa_s->global->p2p_dev_addr, wpa_s->own_addr, ETH_ALEN);
os_memcpy(p2p.dev_addr, wpa_s->global->p2p_dev_addr, ETH_ALEN);
// 设置P2P模块配置信息,包括device name、model name、uuid等
p2p.dev_name = wpa_s->conf->device_name;
p2p.manufacturer = wpa_s->conf->manufacturer;
p2p.model_name = wpa_s->conf->model_name;
p2p.model_number = wpa_s->conf->model_number;
p2p.serial_number = wpa_s->conf->serial_number;
if (wpa_s->wps) {
os_memcpy(p2p.uuid, wpa_s->wps->uuid, 16);
p2p.config_methods = wpa_s->wps->config_methods;
}
// 设置Operation Channel信息和listen channel信息
if (wpa_s->conf->p2p_listen_reg_class &&
wpa_s->conf->p2p_listen_channel) {
p2p.reg_class = wpa_s->conf->p2p_listen_reg_class;
p2p.channel = wpa_s->conf->p2p_listen_channel;
} else {…// 设置默认值}

// 设置国家码
if (wpa_s->conf->country[0] && wpa_s->conf->country[1]) {
os_memcpy(p2p.country, wpa_s->conf->country, 2);
p2p.country[2] = 0x04;
} else// 配置文件中没有设置国家,所以取值为"XX\x04"
os_memcpy(p2p.country, “XX\x04”, 3);// 回顾图7-7
// 判断wifi 驱动是否支持配置文件中设置的operationg channel和listen channel
if (wpas_p2p_setup_channels(wpa_s, &p2p.channels)) {…}
os_memcpy(p2p.pri_dev_type, wpa_s->conf->device_type,WPS_DEV_TYPE_LEN);
p2p.num_sec_dev_types = wpa_s->conf->num_sec_device_types;
os_memcpy(p2p.sec_dev_type, wpa_s->conf->sec_device_type,
p2p.num_sec_dev_types * WPS_DEV_TYPE_LEN);
// 是否支持concurrent operation
p2p.concurrent_operations = !!(wpa_s->drv_flags&WPA_DRIVER_FLAGS_P2P_CONCURRENT);
p2p.max_peers = 100;// 最多能保存100个对端P2P Device信息
/

配置文件中没有设置p2p_ssid_postfix,但P2pStateMachine在initializeP2pSettings函数中
将设置P2P SSID后缀。以笔者的Galaxy Note 2为例,其P2P SSID后缀为“Android_4aa9”。
/
if (wpa_s->conf->p2p_ssid_postfix) {…}
p2p.p2p_intra_bss = wpa_s->conf->p2p_intra_bss;
// ③global->p2p指向一个p2p_data结构体,它是WPAS中P2P模块的代表
global->p2p = p2p_init(&p2p);

for (i = 0; i < MAX_WPS_VENDOR_EXT; i++) {// 拷贝vendor厂商特定的WSC属性信息
if (wpa_s->conf->wps_vendor_ext[i] == NULL) continue;
p2p_add_wps_vendor_extension(global->p2p, wpa_s->conf->wps_vendor_ext[i]);
}
return 0;
}
由上述代码可知,wpas_p2p_init的工作非常简单,主要包括:
·初始化一个p2p_config对象,然后根据p2p_supplicant.conf文件的信息来设置其中
的内容,同时还需要为P2P模块设置一些回调函数。
·调用p2p_init函数以初始化P2P模块。
下面来介绍上述代码中涉及的一些知识。
1.Driver Flags和重要数据结构
先来看上述代码中提到的drv_flags变量。W PAS中,W i-Fi驱动对P2P功能的支持情
况就是由它来表达的。Galaxy Note 2中该变量取值为0x2EAC0,其表达的含义如下。
[–>driver.h]
#define WPA_DRIVER_FLAGS_AP 0x00000040 // wifi driver支持AP。它使得P2P设备能扮演GO
/

标志标明association成功后,Kernel driver需要设置WEP key。
这个标志出现的原因是Kernel API发生了变动,使得只能在关联成功后才能设置key。
/
#define WPA_DRIVER_FLAGS_SET_KEYS_AFTER_ASSOC_DONE 0x00000080
#define WPA_DRIVER_FLAGS_P2P_CONCURRENT 0x00000200// wifi driver支持STA和P2P的并发运行
#define WPA_DRIVER_FLAGS_P2P_CAPABLE 0x00000800 // wifi driver支持P2P
/

7.2.2节Probe Request帧设置曾提到,P2P包含Device Address和Interface Address
两种类型的地址。在实际实现过程中,这两个地址分别代表两个Virtual Interface。显然,P2P中第一个
和一直存在的是拥有Device Address的Vitural Interface。下面这个标志表示该Virtual Interface
可以参与P2P管理(除P2P Group Operation之外的工作)工作以及非P2P相关的工作(例如利用这个
Virtual Interface 加入到一个BSS)。
/
#define WPA_DRIVER_FLAGS_P2P_MGMT_AND_NON_P2P 0x00002000
/

该标志主要针对associate操作。当关联操作失败后,如果driver支持该选项,则表明driver能处理失败
之后的各种收尾工作(Key、timeout等工作)。否则,WPAS需要自己处理这些事情。
/
#define WPA_DRIVER_FLAGS_SANE_ERROR_CODES 0x00004000
/

下面这个标志和off channel机制有关,可参考4.3.4节关于capability的介绍。当802.11
MAC帧通过off channel发送,下面这个标志表示driver会反馈一个发送情况(TX Report)消息给WPAS。
/
#define WPA_DRIVER_FLAGS_OFFCHANNEL_TX 0x00008000
/

下面这两个标志表示Kernel中的driver是否能反馈Deauthentication/Disassociation帧
发送情况(TX Report)。
*/
#define WPA_DRIVER_FLAGS_DEAUTH_TX_STATUS 0x00020000
下面来看wpas_p2p_init中出现的几个重要数据结构。首先是p2p_config和
p2p_data,它们的成员如图7-29所示。
图7-29 p2p_config和p2p_data结构
图7-29展示了p2p_config和p2p_data两个数据结构中一些重要的成员。
·p2p_config定义了20个回调函数。这些回调函数定义了P2P模块和外界交互的接
口。在wpas_p2p_init中,这些回调函数均指向p2p_supplicant.c中对应的函数,例如
p2p_scan指向wpas_p2p_scan,dev_lost指向wpas_dev_lost。另外,由于回调函数的参
数比较复杂,所以图中均省略了参数信息。
·p2p_data指向一个p2p_config对象。
下面来看另外几个重要数据结构的内容,图7-30展示了五种数据结构。
·p2p_device代表一个P2P设备。其中设备名、Device CapabilityBitmap等信息保
存在一个类型为p2p_peer_info的对象中。
·p2p_group代表一个P2P Group的信息,其内部包含一个p2p_group_config对象和
一个p2p_group_member链表。p2p_group_config表示该Group的配置信
息,p2p_group_member代表Group Member即P2P Client的信息。
图7-30 p2p_device及其他数据结构
提示 W PAS中定义了非常多的数据结构类型,这极大增加了初学者的学习难度。根据
笔者的经验,建议在学习过程中先简单了解这些数据结构的名字及作用,然后在具体代码分
析时再结合代码逻辑来了解这些数据结构及其内部各个成员变量的具体作用。
下面来看p2p_init函数。
2.p2p_init函数
p2p_init函数将初始化W PAS中的P2P模块,其代码如下所示。
[–>p2p.c::p2p_init]
struct p2p_data * p2p_init(const struct p2p_config *cfg)
{
struct p2p_data p2p;

/

从下面这行代码可看出,一个p2p_data对象的内存分布,该内存将包含一个p2p_data的所有信息以及一个
p2p_config对象的所有信息。
*/
p2p = os_zalloc(sizeof(*p2p) + sizeof(*cfg));
// 将p2p_data的cfg成员变量指向保存p2p_config信息的那块内存地址
p2p->cfg = (struct p2p_config *) (p2p + 1);
os_memcpy(p2p->cfg, cfg, sizeof(*cfg)); // 拷贝传入的p2p_config信息
if (cfg->dev_name) p2p->cfg->dev_name = os_strdup(cfg->dev_name);
…// 其他信息拷贝
#ifdef ANDROID_P2P
p2p->min_disc_int = 2; // listen state的最小时间为200毫秒
p2p->sd_dev_list = NULL;
#else
p2p->min_disc_int = 1;
#endif
p2p->max_disc_int = 3;
// 随机获取next_tie_breaker的初值
// 第二个参数1表示next_tie_breaker的字节长度,其类型是u8
os_get_random(&p2p->next_tie_breaker, 1);
p2p->next_tie_breaker &= 0x01;
// 设置本机P2P Device的device capability信息
if (cfg->sd_request) p2p->dev_capab |= P2P_DEV_CAPAB_SERVICE_DISCOVERY;
p2p->dev_capab |= P2P_DEV_CAPAB_INVITATION_PROCEDURE;
if (cfg->concurrent_operations)// 支持concurrent功能
p2p->dev_capab |= P2P_DEV_CAPAB_CONCURRENT_OPER;
p2p->dev_capab |= P2P_DEV_CAPAB_CLIENT_DISCOVERABILITY;
dl_list_init(&p2p->devices);
// 注册一个超时时间(如果定义了ANDROID_P2P宏,该时间为30ms)
// 用来检测是否有不活跃的p2p_device
eloop_register_timeout(P2P_PEER_EXPIRATION_INTERVAL, 0,
p2p_expiration_timeout, p2p, NULL);
return p2p;
}
p2p模块初始化还算比较简单。
3.注册Action帧监听事件
4.3.4节分析wpa_driver_nl80211_finish_drv_init时曾介绍
过,wpa_driver_nl80211_set_mode函数和P2P关系较大。为什么这么说呢?相信代码能
给出最直接的解释。
[–>driver_nl80211.c::wpa_driver_nl80211_set_mode]
static int wpa_driver_nl80211_set_mode(struct i802_bss *bss, enum nl80211_iftype nlmode)
{// 注意,在wpa_driver_nl80211_finish_drv_init函数中,nlmode被设置为NL80211_IFTYPE_STATION
struct wpa_driver_nl80211_data drv = bss->drv;
int ret = -1; int i;
/

drv->nlmode的类型为enum nl80211_iftype,4.3.4节关于wpa_driver_nl80211_finish_drv_init
的分析中有对该变量的解释。drv->nlmode只有为NL80211_IFTYPE_AP或NL80211_IFTYPE_P2P_GO时,
is_ap_interface函数才返回非0值。很显然此时virtual interface的类型不可能是GO。
/
int was_ap = is_ap_interface(drv->nlmode);
int res;
// 设置虚拟interface的类型为NL80211_IFTYPE_STATION
res = nl80211_set_mode(drv, drv->ifindex, nlmode);
if (res == 0) {
drv->nlmode = nlmode;
ret = 0;
goto done;// 设置成功,直接跳转到done处
}

done:

if (is_ap_interface(nlmode)) {
nl80211_mgmt_unsubscribe(bss, “start AP”);
if (nl80211_setup_ap(bss)) return -1;
} else if (was_ap) {
/
Remove additional AP mode functionality */
nl80211_teardown_ap(bss);
} else {
// 本例将执行下面这个函数以取消监听Action帧事件
// 由于之前并未注册,所以此时执行这个函数将没有实际作用
nl80211_mgmt_unsubscribe(bss, “mode change”);
}
if (!is_ap_interface(nlmode) &&
nl80211_mgmt_subscribe_non_ap(bss) < 0)// 注册对Action帧的监听事件
wpa_printf(MSG_DEBUG, "nl80211: Failed to register Action "
“frame processing - ignore for now”);
return 0;
}
nl80211_mgmt_subscribte_non_ap将注册对Action帧的监听事件,其作用就是当设
备收到Action帧后,W i-Fi驱动将发送对应的netlink消息给W PAS。来看
nl80211_mgmt_subscribte_non_ap函数,代码如下所示。
[–>driver_nl80211.c::nl80211_mgmt_subscribte_non_ap]
static int nl80211_mgmt_subscribe_non_ap(struct i802_bss *bss)
{
struct wpa_driver_nl80211_data drv = bss->drv;
/

下面这个函数将注册libnl回调事件到event loop。在4.3.4节关于
wpa_driver_nl80211_init_nl与nl80211_init_bss分析中曾详细介绍过该函数。
总之,当WPAS收到对应的netlink消息后,process_bss_event函数将被调用。
/
if (nl80211_alloc_mgmt_handle(bss)) return -1;
#if defined(CONFIG_P2P) || defined(CONFIG_INTERWORKING)
…// 注册对GAS Public Action帧的监听,Service Discovery和它有关
#endif /
CONFIG_P2P || CONFIG_INTERWORKING /
#ifdef CONFIG_P2P
/

注册对P2P Public Action帧的监听,第二个参数中的04-09-50-6F-9A-09指明了P2P Public Action
帧Frame Body的Category、Action Field、OUI、OUI-Type(参考表7-3)的取值。即只有收到的
Frame Body对应字段分别等于上述指定值的Action帧,Wi-Fi驱动才会发送netlink消息给WPAS。
*/
if (nl80211_register_action_frame(bss, (u8 ) “\x04\x09\x50\x6f\x9a\x09”,6) < 0)
return -1;
/

注册对P2P Action帧的监听,第二个参数中7F-50-6F-9A-09指明了Action帧Frame Body的
Category和OUI。根据802.11规范,7F代表Vendor Specific,50-6F-9A是WFA的OUI,最后一个
09代表P2P。
*/
if (nl80211_register_action_frame(bss,(u8 )“\x7f\x50\x6f\x9a\x09”,5) < 0) return -1;
#endif /
CONFIG_P2P /
#ifdef CONFIG_IEEE80211W

#endif /
CONFIG_IEEE80211W /
#ifdef CONFIG_TDLS

#endif /
CONFIG_TDLS */
…// 其他感兴趣帧的注册
return 0;
}
由上述代码可知nl80211_mgmt_subscribe_non_ap在P2P方面注册了两种类型的帧监
听事件。
·P2P Public Action帧监听事件:根据P2P规范,目前使用的均是802.11 Public
Action帧,即Category的值为0x04。目前GON、P2P Invitation、Provision Discovery
以及Device Discoverability使用P2P Public Action帧。
·P2P Action帧监听事件:这种类型的帧属于802.11 Action帧的一种,其Category
取值为0x7F,OUI指定为W FA的OUI(即50-6F-9A),而OUI-Type指定为P2P(取值
为0x09)。目前Notice of Absence、P2P Presence、GO Discoverability使用P2P
Action帧。
注意 上述注册的Action帧监听事件对应的处理函数是process_bss_event。
至此,P2P模块以及Action帧监听事件注册等工作都已完成,W PAS马上可为
W ifiP2pService提供P2P相关的服务了。下面将结合7.2节中介绍的如下几个重要的P2P工
作流程来分析代码。
·搜索周围的P2P设备。
·向某个P2P设备发起Provision Discovery流程。
·对端设备开展Group Formation流程,重点关注其中的Group Negotiation。
提示 GON结束后,如果本机设备扮演Client,则后续工作包括Provisioning(即
W SC安全配置协商,本机充当Enrollee,参考第6章)和加入Group(类似STA加入AP,
参考第4章)。如果本机扮演GO,则后续工作也分为Provisioning(充当Registrar)和处
理Client加入Group(扮演AP的角色)。本书不讨论和GO相关的知识,请读者在阅读完相
关章节基础上自行研究它们。
7.4.2 P2P Device Discovery流程分析
根据7.3.2节中对DISCOVER_PEERS命令的代码分析可知,P2pStateMachine将发
送"P2P_FIND 120"命令给W PAS以触发P2P Device Discovery流程。处理该命令的代码如
下所示。
[–>ctrl_iface.c::wpa_supplicant_ctrl_iface_process]
char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
char *buf, size_t *resp_len)
{
char *reply; const int reply_size = 4096;
int ctrl_rsp = 0; int reply_len;

#ifdef CONFIG_P2P
// 处理带参数的P2P_FIND命令
} else if (os_strncmp(buf, "P2P_FIND “, 9) == 0) {
// 注意“P2P_FIND ”多了一个空格
if (p2p_ctrl_find(wpa_s, buf + 9)) reply_len = -1;
} else if (os_strcmp(buf, “P2P_FIND”) == 0) {
// 处理不带参数的P2P_FIND命令
if (p2p_ctrl_find(wpa_s, “”)) reply_len = -1;
} …// 其他P2P命令处理
#endif

}
不论P2P_FIND命令是否携带参数,其最终的处理函数都将对应为p2p_ctrl_find,如
下所示。
[–>ctrl_iface.c::p2p_ctrl_find]
static int p2p_ctrl_find(struct wpa_supplicant *wpa_s, char *cmd)
{
unsigned int timeout = atoi(cmd);
enum p2p_discovery_type type = P2P_FIND_START_WITH_FULL;
// 搜索方式,见下文解释
u8 dev_id[ETH_ALEN], *_dev_id = NULL;
char *pos;
// 设置搜索方式,见下文解释
if (os_strstr(cmd, “type=social”)) type = P2P_FIND_ONLY_SOCIAL;
else if (os_strstr(cmd, “type=progressive”)) type = P2P_FIND_PROGRESSIVE;
pos = os_strstr(cmd, “dev_id=”);// dev_id代表peer端device的MAC地址
if (pos) {…}
// wpas_p2p_find内部将调用p2p_find,下文将直接分析p2p_find
return wpas_p2p_find(wpa_s, timeout, type, 0, NULL, _dev_id);
}
P2P_FIND支持三种不同的Discovery Type,分别如下。
·P2P_FIND_START_W ITH_FULL:默认设置。表示先扫描所有频段,然后再扫描
social channels。这种搜索方式如图7-3所示。
·P2P_FIND_ONLY_SOCIAL:只扫描social channels。它将跳过“扫描所有频
段”这一过程。这种搜索方式能加快搜索的速度。
·P2P_FIND_PROGRESSIVE:它和P2P_FIND_START_W ITH_FULL类似,只不
过在Search State阶段将逐个扫描所有频段。为什么在search state阶段会扫描所有频段
呢?请读者参考图7-22中的状态切换路线14。当周围已经存在Group的时候,如果在最初
的“扫描所有频段”这一过程中没有发现Group,则在后续的search state逐个扫描频段过
程中就有可能发现之前那些没有找到的Group。
注意 GO将工作在Operation Channel,而Listen Channel只在最初的P2P Device
Discovery阶段使用。
1.P2P设备扫描流程
P2P设备扫描流程从wpas_p2p_find开始,其代码如下所示。
[–>p2p_supplicant.c::wpas_p2p_find]
int wpas_p2p_find(struct wpa_supplicant *wpa_s, unsigned int timeout,
enum p2p_discovery_type type,unsigned int num_req_dev_types,
const u8 *req_dev_types, const u8 dev_id)
{
/

取消还未发送的Action帧数据。WPAS中,待发送的Action帧数据保存在wpa_supplicant对象的
pending_action_tx变量中,它指向一块数据缓冲区。
/
wpas_p2p_clear_pending_action_tx(wpa_s);
wpa_s->p2p_long_listen = 0;
/

如果wifi driver能直接处理P2P管理,则主要工作将由wifi driver来完成。Galaxy Note 2
不支持WPA_DRIVER_FLAGS_P2P_MGMT。
*/
if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_P2P_MGMT)
return wpa_drv_p2p_find(wpa_s, timeout, type);

// 取消计划扫描任务
wpa_supplicant_cancel_sched_scan(wpa_s);
// 调用p2p_find函数
return p2p_find(wpa_s->global->p2p, timeout, type,
num_req_dev_types, req_dev_types, dev_id);
}
来看p2p_find函数,其代码如下所示。
[–>p2p.c::p2p_find]
int p2p_find(struct p2p_data *p2p, unsigned int timeout, enum p2p_discovery_type type,
unsigned int num_req_dev_types, const u8 *req_dev_types, const u8 *dev_id)
{
int res;
p2p_free_req_dev_types(p2p);
if (req_dev_types && num_req_dev_types) {…// 本例没有设置request dev type属性}
if (dev_id) {
os_memcpy(p2p->find_dev_id_buf, dev_id, ETH_ALEN);
p2p->find_dev_id = p2p->find_dev_id_buf;
} else p2p->find_dev_id = NULL;
// 注意下面这个P2P_AFTER_SCAN_NOTHING标志,它表示P2P设备完成scan动作后,无须做其他动作
p2p->start_after_scan = P2P_AFTER_SCAN_NOTHING;
p2p_clear_timeout(p2p);
p2p->cfg->stop_listen(p2p->cfg->cb_ctx);// 停止监听
p2p->find_type = type;
p2p_device_clear_reported(p2p);
p2p_set_state(p2p, P2P_SEARCH); // 设置P2P模块的状态为P2P_SEARCH
eloop_cancel_timeout(p2p_find_timeout, p2p, NULL);
p2p->last_p2p_find_timeout = timeout;
// 注册一个扫描超时处理任务
if (timeout) eloop_register_timeout(timeout, 0, p2p_find_timeout,p2p, NULL);
switch (type) {
case P2P_FIND_START_WITH_FULL:
case P2P_FIND_PROGRESSIVE: // p2p_scan函数指针指向wpas_p2p_scan
res = p2p->cfg->p2p_scan(p2p->cfg->cb_ctx, P2P_SCAN_FULL, 0,
p2p->num_req_dev_types,p2p->req_dev_types, dev_id);
break;
case P2P_FIND_ONLY_SOCIAL:
res = p2p->cfg->p2p_scan(p2p->cfg->cb_ctx, P2P_SCAN_SOCIAL, 0,
p2p->num_req_dev_types,p2p->req_dev_types, dev_id);
break;
default:
return -1;
}
if (res == 0) {
// 设置p2p_scan_running值为1,该变量后面用到的地方比较多,请读者注意
p2p->p2p_scan_running = 1;
eloop_cancel_timeout(p2p_scan_timeout, p2p, NULL);
eloop_register_timeout(P2P_SCAN_TIMEOUT, 0, p2p_scan_timeout,p2p, NULL);
} … // 略去res为其他值的处理情况
return res;
}
上述代码中p2p_config对象的p2p_scan函数指针变量将指向p2p_supplicant.c中的
wpas_p2p_scan,马上来看它,代码如下所示。
[–>p2p_supplicant.c::wpas_p2p_scan]
static int wpas_p2p_scan(void *ctx, enum p2p_scan_type type, int freq,
unsigned int num_req_dev_types, const u8 *req_dev_types, const u8 *dev_id)
{
struct wpa_supplicant *wpa_s = ctx;
// 扫描参数
struct wpa_driver_scan_params params;
int ret; struct wpabuf *wps_ie, *ies;
int social_channels[] = { 2412, 2437, 2462, 0, 0 };
size_t ielen; int was_in_p2p_scan;
os_memset(&params, 0, sizeof(params));
params.num_ssids = 1; // 设置SSID参数,P2P_WILDCARD_SSID的值为“DIRECT-”
params.ssids[0].ssid = (u8 *) P2P_WILDCARD_SSID;
params.ssids[0].ssid_len = P2P_WILDCARD_SSID_LEN;
wpa_s->wps->dev.p2p = 1;
// 构造Probe Request帧中WSC IE信息
wps_ie = wps_build_probe_req_ie(0, &wpa_s->wps->dev, wpa_s->wps->uuid,
WPS_REQ_ENROLLEE,num_req_dev_types, req_dev_types);
ielen = p2p_scan_ie_buf_len(wpa_s->global->p2p);
ies = wpabuf_alloc(wpabuf_len(wps_ie) + ielen);

// 构造P2P IE信息,感兴趣的读者不妨自行阅读p2p_scan_ie函数
p2p_scan_ie(wpa_s->global->p2p, ies, dev_id);
params.p2p_probe = 1;
params.extra_ies = wpabuf_head(ies); params.extra_ies_len = wpabuf_len(ies);
switch (type) {
case P2P_SCAN_SOCIAL: // 只扫描social channels的话,将设置params.freqs变量
params.freqs = social_channels;
break;
case P2P_SCAN_FULL:
break;
…// 其他扫描频段控制
}
was_in_p2p_scan = wpa_s->scan_res_handler == wpas_p2p_scan_res_handler;
// 设置P2P扫描结果处理函数
wpa_s->scan_res_handler = wpas_p2p_scan_res_handler;
// 发起P2P设备扫描,该函数内部将调用driver_nl80211.c的wpa_driver_nl80211_scan函数
ret = wpa_drv_scan(wpa_s, &params);
wpabuf_free(ies);

return ret;
}
读者可比较本节和4.5.3节无线网络扫描流程分析的内容。总体而言,P2P设备扫描的代
码逻辑比无线网络扫描的代码逻辑要简单得多。图7-31为W PAS中P2P设备扫描所涉及的几
个重要函数调用。
图7-31 P2P Device扫描流程
下面来看P2P设备扫描结果的处理流程。
2.P2P设备扫描结果处理流程
由4.5.3节“_wpa_supplicant_event_scan_results分析之二”中的代码可知,当
scan_res_handler不为空的时候,扫描结果将交给scan_res_handler来处理。由图7-31可
知,对P2P设备扫描时将设置scan_res_handler为wpas_p2p_scan_res_handler,其代码
如下所示。
[–>p2p_supplicant.c::wpas_p2p_scan_res_handler]
static void wpas_p2p_scan_res_handler(struct wpa_supplicant *wpa_s,
struct wpa_scan_results *scan_res)
{
size_t i;

for (i = 0; i < scan_res->num; i++) {
struct wpa_scan_res *bss = scan_res->res[i];
// ①对每一个扫描结果调用p2p_scan_res_handler函数
if (p2p_scan_res_handler(wpa_s->global->p2p, bss->bssid,// 处理扫描结果
bss->freq, bss->level,(const u8 *) (bss + 1), bss->ie_len) > 0)
break;
}
p2p_scan_res_handled(wpa_s->global->p2p);// ②处理完毕后调用p2p_scan_res_handled
}
wpas_p2p_scan_res_handler中有两个关键函数,先来看第一个。
(1)p2p_scan_res_handler函数
p2p_scan_res_handler的代码如下所示。
[–>p2p.c::p2p_scan_res_handler]
int p2p_scan_res_handler(struct p2p_data *p2p, const u8 *bssid, int freq,
int level, const u8 ies, size_t ies_len)
{
// 添加一个P2P Device,详情见下文代码分析
p2p_add_device(p2p, bssid, freq, level, ies, ies_len);
/

go_neg_peer代表GON的对端设备,如果go_neg_peer不为空而且设备扫描时由发现了它,则直接通过
p2p_connect_send向其发送GON Request帧以开展GON流程。
*/
if (p2p->go_neg_peer && p2p->state == P2P_SEARCH &&
os_memcmp(p2p->go_neg_peer->info.p2p_device_addr, bssid, ETH_ALEN) == 0) {
p2p_connect_send(p2p, p2p->go_neg_peer);
return 1;
}
return 0;
}
上述代码中最重要的是p2p_add_device函数,其代码如下所示。
[–>p2p.c::p2p_add_device]
int p2p_add_device(struct p2p_data *p2p, const u8 *addr, int freq, int level,
const u8 *ies, size_t ies_len)
{
struct p2p_device *dev; struct p2p_message msg;
const u8 p2p_dev_addr; int i;
os_memset(&msg, 0, sizeof(msg));
// 解析扫描结果中的IE信息,解析完的结果保存在一个p2p_message对象中
if (p2p_parse_ies(ies, ies_len, &msg)) {…// 解析扫描结果}
// p2p device info中的属性
if (msg.p2p_device_addr) p2p_dev_addr = msg.p2p_device_addr;
else if (msg.device_id) p2p_dev_addr = msg.device_id;

// 过滤那些被阻止的P2P Device
if (!is_zero_ether_addr(p2p->peer_filter) &&
os_memcmp(p2p_dev_addr, p2p->peer_filter, ETH_ALEN) != 0) {…}
// 构造一个p2p_device对象,并将其加入p2p_data结构体的devices链表中
dev = p2p_create_device(p2p, p2p_dev_addr);// 创建一个P2P Device对象

os_get_time(&dev->last_seen);// 设置发现该P2P Device的时间
// p2p_device的flags变量代表该p2p_device的一些信息
dev->flags &= ~(P2P_DEV_PROBE_REQ_ONLY | P2P_DEV_GROUP_CLIENT_ONLY);
if (os_memcmp(addr, p2p_dev_addr, ETH_ALEN) != 0)
os_memcpy(dev->interface_addr, addr, ETH_ALEN);
…// 处理ssid、listen channel等内容
dev->listen_freq = freq;
// 如果对端P2P Device是GO,它回复的Probe Response帧P2P IE信息中将包含Group Info属性
if (msg.group_info) dev->oper_freq = freq;
dev->info.level = level;
p2p_copy_wps_info(dev, 0, &msg);// 复制WSC IE
…// 处理Vendor相关的IE信息
// 根据Group Info信息添加Client。就本例而言,周围还不存在GO
p2p_add_group_clients(p2p, p2p_dev_addr, addr, freq, msg.group_info,msg.group_info_len);
p2p_parse_free(&msg);
// 判断是否有Service Discovery Request,如果有,需要为flags设置P2P_DEV_SD_SCHEDULE标志位
if (p2p_pending_sd_req(p2p, dev)) dev->flags |= P2P_DEV_SD_SCHEDULE;
// P2P_DEV_REPORTED表示WPAS已经向客户端汇报过该P2P Device信息了
if (dev->flags & P2P_DEV_REPORTED) return 0;
// P2P_DEV_USER_REJECTED表示用户拒绝该P2P Device信息
if (dev->flags & P2P_DEV_USER_REJECTED) {return 0;}
/

dev_found函数指针指向wpas_dev_found,该函数将向WifiMonitor发送消息以告知我们找到了一个
P2P Device,该消息也称为P2P Device Found消息。
*/
p2p->cfg->dev_found(p2p->cfg->cb_ctx, addr, &dev->info,
!(dev->flags & P2P_DEV_REPORTED_ONCE));
// 下面这两个标志表示该P2P Device已经向客户端汇报过并且汇报过一次了
dev->flags |= P2P_DEV_REPORTED | P2P_DEV_REPORTED_ONCE;
return 0;
}
图7-32为W PAS向其客户端汇报的P2P Device Found消息的格式示例。
图7-32 P2P Device Found消息
(2)p2p_scan_res_handled函数
接着来看第二个关键函数p2p_scan_res_handled。
[–>p2p.c::p2p_scan_res_handled]
void p2p_scan_res_handled(struct p2p_data p2p)
{

p2p->p2p_scan_running = 0; // 设置p2p_scan_running值为0
eloop_cancel_timeout(p2p_scan_timeout, p2p, NULL);// 取消扫描超时处理任务
/

还记得p2p_find函数中介绍的P2P_AFTER_SCAN_NOTHING标志吗(参考7.4.2节)?它将用在
下面这个p2p_run_after_scan函数中。由于指定了P2P_AFTER_SCAN_NOTHING标志,所以下面这个
函数返回0。感兴趣的读者可自行研究p2p_run_after_scan函数。
*/
if (p2p_run_after_scan(p2p)) return;
if (p2p->state == P2P_SEARCH) // 就本例而言,将满足此if条件
p2p_continue_find(p2p);
}
p2p_continue_find的代码如下所示。
[–>p2p.c::p2p_continue_find]
void p2p_continue_find(struct p2p_data *p2p)
{
struct p2p_device *dev;
#ifdef ANDROID_P2P
int skip=1;
#endif
p2p_set_state(p2p, P2P_SEARCH);
…// Service Discovery和Provision Discovery处理。就本例而言,这部分代码逻辑意义不大
p2p_listen_in_find(p2p); // 进入listen state。来看此函数的代码
}
[–>p2p.c::p2p_listen_in_find]
static void p2p_listen_in_find(struct p2p_data *p2p)
{
unsigned int r, tu;
int freq;
struct wpabuf *ies;
// 根据p2p_supplicant.conf中listen_channel等配置参数获取对应的频段
freq = p2p_channel_to_freq(p2p->cfg->country,
p2p->cfg->reg_class,p2p->cfg->channel);

// 计算需要在listen state等待的时间
os_get_random((u8 ) &r, sizeof®);
tu = (r % ((p2p->max_disc_int - p2p->min_disc_int) + 1) +
    p2p->min_disc_int) * 100;
p2p->pending_listen_freq = freq;
p2p->pending_listen_sec = 0;
p2p->pending_listen_usec = 1024 * tu;
/

构造P2P Probe Response帧,当我们在Listen state收到其他设备发来的Probe Request帧后,wifi
驱动将直接回复此处设置的P2P Probe Response帧。
*/
ies = p2p_build_probe_resp_ies(p2p);
// start_listen指向wpas_start_listen函数
if (p2p->cfg->start_listen(p2p->cfg->cb_ctx, freq, 1024 *tu/1000,ies)<0){… }
wpabuf_free(ies);
}
由上述代码可知,p2p_listen_in_find的内部实现完全遵循了7.2.2节介绍的和listen
state相关的理论知识。
下面来看wpas_start_listen函数。
[–>p2p_supplicant.c::wpas_start_listen]
static int wpas_start_listen(void *ctx, unsigned int freq,
unsigned int duration, const struct wpabuf *probe_resp_ie)
{
struct wpa_supplicant wpa_s = ctx;
/

调用driver_nl80211.c的wpa_driver_set_ap_wps_p2p_ie函数,它用于将Probe Response帧信息和
Association Response帧信息。此函数为Android新增的功能,由厂商实现。
/
wpa_drv_set_ap_wps_ie(wpa_s, NULL, probe_resp_ie, NULL);
/

调用driver_nl80211.c的wpa_driver_nl80211_probe_req_report函数,其目的是让wifi driver
收到Probe Request帧后,返回一个EVENT_RX_PROBE_REQ netlink消息给WPAS。
/
if (wpa_drv_probe_req_report(wpa_s, 1) < 0) {…}
wpa_s->pending_listen_freq = freq;
wpa_s->pending_listen_duration = duration;
/

调用driver_nl80211.c的wpa_driver_nl80211_remain_on_channel函数,其目的是让
wlan设备在指定频段(第二个参数freq)上停留duration毫秒。注意,这个函数的返回值只是表示
wifi driver是否成功处理了这个请求,它不能用于判断wifi driver是否已经切换到了指定频段。
如果一切正常,当wifi driver切换到指定频段后,它将发送一个名为EVENT_REMAIN_ON_CHANNEL的
netlink消息给WPAS。
*/
if (wpa_drv_remain_on_channel(wpa_s, freq, duration) < 0) {…}
wpa_s->off_channel_freq = 0;
wpa_s->roc_waiting_drv_freq = freq;
return 0;
}
wpas_start_listen比较简单,不过有一些知识点请读者注意。
·wpa_drv_set_ap_wps_ie为wifi driver设置了P2P IE信息。如果wifi driver自己处
理Probe Resquest帧(即不发送EVENT_RX_PROBE_REQ消息给W PAS),则wifi
driver将把此处设置的P2P IE信息填写到Probe Response帧中。
·wpa_drv_probe_req_report要求wifi driver收到Probe Request帧后,发送
EVENT_RX_PROBE_REQ消息给W PAS。W PAS内部将处理此消息,最终会回复一个
Probe Response帧。这部分代码请读者自行阅读。
·wpa_drv_remain_on_channel要求wifi driver在指定频段工作一段时间。当wifi
driver切换到指定频段后,会发送EVENT_REMAIN_ON_CHANNEL消息给
W PAS,W PAS内部将处理一些事情。这部分代码也请读者自行阅读。
提醒 笔者感觉wpa_drv_set_ap_wps_ie和wpa_drv_probe_req_report功能重复。
不过由于driver.h对set_ap_wps_ie的功能描述不是特别清晰,请了解细节的读者和我们分
享相关的知识。另外,请读者认真阅读EVENT_RX_PROBE_REQ和
EVENT_REMAIN_ON_CHANNEL消息的处理代码。
(3)P2P设备扫描结果处理流程总结
图7-33展示了P2P设备扫描结果处理的流程。
图7-33 P2P设备扫描结果处理流程
注意 图7-33中,只有满足一定条件,第6个及以后的函数才能执行。请读者结合
p2p_scan_res_handled的代码来加深对图7-32的体会。另外,由于Android平台中
wpa_driver_set_ap_wps_p2p_ie由厂商实现,故图7-33没有列出该函数。
设备找到以后,下一步工作就是发起Provision Discovery流程,马上来看它。
7.4.3 Provision Discovery流程分析
P2pStateMachine的ProvisionDiscoveryState在其EA中将发送形
如"P2P_PROV_DISC 8a:32:9b:6c:d1:80 pbc"的命令给W PAS(请参考7.3.2
节“CONNECT处理流程分析”)去执行,其核心处理函数是p2p_ctrl_prov_disc,代码
如下所示。
[–>ctrl_iface.c::p2p_ctrl_prov_disc]
static int p2p_ctrl_prov_disc(struct wpa_supplicant *wpa_s, char *cmd)
{
u8 addr[ETH_ALEN]; char *pos;
if (hwaddr_aton(cmd, addr)) return -1;
…// 参数处理。P2P_PROV_DISC命令的完整参数形式为“ [join]”
// 其最后一个join参数为可选项。WifiP2pService没有使用它
// 调用wpas_p2p_prov_disc,其内部将调用p2p_prov_disc_req,我们直接来看它
return wpas_p2p_prov_disc(wpa_s, addr, pos, os_strstr(pos, “join”) != NULL);
}
1.PD Request帧发送流程
p2p_prov_disc_req的代码如下所示。
[–>p2p.c::p2p_prov_disc_req]
int p2p_prov_disc_req(struct p2p_data *p2p, const u8 *peer_addr,
u16 config_methods, int join, int force_freq)
{
struct p2p_device *dev;
dev = p2p_get_device(p2p, peer_addr);// 根据目标设备地址找到对应的p2p_device对象
if (dev == NULL)
dev = p2p_get_device_interface(p2p, peer_addr);

dev->wps_prov_info = 0;
dev->req_config_methods = config_methods;
// 就本例而言,join的值为0
if (join) dev->flags |= P2P_DEV_PD_FOR_JOIN;
else dev->flags &= ~P2P_DEV_PD_FOR_JOIN; // 取消dev->flags中的P2P_DEV_PD_FOR_JOIN标志

p2p->user_initiated_pd = !join;
if (p2p->user_initiated_pd && p2p->state == P2P_IDLE)
p2p->pd_retries = MAX_PROV_DISC_REQ_RETRIES;
// 最后调用p2p_send_prov_disc_req发送数据
return p2p_send_prov_disc_req(p2p, dev, join, force_freq);
}
p2p_send_prov_disc_req比较简单,代码如下。
[–>p2p_pd.c::p2p_send_prov_disc_req]
int p2p_send_prov_disc_req(struct p2p_data *p2p, struct p2p_device *dev,
int join, int force_freq)
{
struct wpabuf *req; int freq;
// 确定对端设备所在的工作频段
if (force_freq > 0) freq = force_freq;
else freq = dev->listen_freq > 0 ? dev->listen_freq :dev->oper_freq;

dev->dialog_token++;// 还记得表7-3关于Dialog Token的描述吗
if (dev->dialog_token == 0) dev->dialog_token = 1;
// 构造Provision Discovery Request帧内容
req = p2p_build_prov_disc_req(p2p, dev->dialog_token,
dev->req_config_methods,join ? dev : NULL);

p2p->pending_action_state = P2P_PENDING_PD;// 该标志表明当前pending的Action是PD
// p2p_send_action内部将调用wpas_send_action。这部分内容比较简单,请读者自行研究
// 另外,第7.4.2节也会提到wpas_send_action函数,读者也可学习完本章后再来研究它
if (p2p_send_action(p2p, freq, dev->info.p2p_device_addr,
p2p->cfg->dev_addr, dev->info.p2p_device_addr,
wpabuf_head(req), wpabuf_len(req), 200) < 0) {…}
// 保存对端P2P设备地址
os_memcpy(p2p->pending_pd_devaddr, dev->info.p2p_device_addr, ETH_ALEN);
wpabuf_free(req);
return 0;
}
上述代码中,PD Request帧最终将通过p2p_send_action函数发送出去。不
过,p2p_send_action并不简单,它将涉及Off Channel发送以及处理对应netlink消息的
过程。做为本书W i-Fi部分的最后一章,请读者在学习完本章后,再自行研究这个函数。
图7-34展示了PD Request帧发送流程中的重要函数调用序列。
图7-34 PD Request帧发送流程
图7-34列出了wpas_send_action中的几个重要函数调用,请读者自行研究时候注意相
关内容。
下面来看PD Respone帧的处理流程。由于PD Response属于Action帧,所以我们将
介绍W PAS中Action帧的接收流程,然后再分析PD Response的处理流程。
2.Action帧接收流程
PD Response帧属于Public Action帧的一种,而根据7.4.1节“注册Action帧监听事
件”的分析可知,当收到对端设备发来的PD Response帧后,process_bss_event函数将
被调用。此函数的代码如下所示。
[–>driver_nl80211.c::process_bss_event]
static int process_bss_event(struct nl_msg *msg, void *arg)
{
struct i802_bss *bss = arg; struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
struct nlattr *tb[NL80211_ATTR_MAX + 1];
nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
switch (gnlh->cmd) {
case NL80211_CMD_FRAME: // 收到对端发送的帧
case NL80211_CMD_FRAME_TX_STATUS: // 对应本机所发送的管理帧的TX Report
mlme_event(bss->drv, gnlh->cmd, tb[NL80211_ATTR_FRAME],
tb[NL80211_ATTR_MAC], tb[NL80211_ATTR_TIMED_OUT],
tb[NL80211_ATTR_WIPHY_FREQ], tb[NL80211_ATTR_ACK],
tb[NL80211_ATTR_COOKIE]);
break;

}
return NL_SKIP;
}
由上述代码可知,不论是代表由本机所发送的管理帧TX Report的
NL80211_CMD_FRAME_TX_STATUS消息,还是代表本机接收到对端发来的管理帧事件
的NL80211_CMD_FRAME消息,最终都会调用mlme_event函数,其代码如下所示。
[–>driver_nl80211.c::mlme_event]
static void mlme_event(struct wpa_driver_nl80211_data *drv, enum nl80211_commands cmd,
struct nlattr *frame, struct nlattr *addr, struct nlattr *timed_out,
struct nlattr *freq, struct nlattr *ack,struct nlattr *cookie)
{

switch (cmd) {
case NL80211_CMD_AUTHENTICATE:
mlme_event_auth(drv, nla_data(frame), nla_len(frame));
break;
case NL80211_CMD_ASSOCIATE:
mlme_event_assoc(drv, nla_data(frame), nla_len(frame));
break;

case NL80211_CMD_FRAME:
mlme_event_mgmt(drv, freq, nla_data(frame), nla_len(frame));
break;
case NL80211_CMD_FRAME_TX_STATUS:
mlme_event_mgmt_tx_status(drv, cookie, nla_data(frame),
nla_len(frame), ack);

}
}
mlme_event将处理各种类型的帧事件。对于本例而言,此时将调用
mlme_event_mgmt函数,其代码如下所示。
[–>driver_nl80211.c::mlme_event_mgmt]
static void mlme_event_mgmt(struct wpa_driver_nl80211_data *drv,
struct nlattr *freq, const u8 *frame, size_t len)
{
const struct ieee80211_mgmt *mgmt;
union wpa_event_data event;
u16 fc, stype;
mgmt = (const struct ieee80211_mgmt *) frame;

fc = le_to_host16(mgmt->frame_control);
stype = WLAN_FC_GET_STYPE(fc);
os_memset(&event, 0, sizeof(event));
if (freq) {
event.rx_action.freq = nla_get_u32(freq);
drv->last_mgmt_freq = event.rx_action.freq;
}
if (stype == WLAN_FC_STYPE_ACTION) {
event.rx_action.da = mgmt->da;
event.rx_action.sa = mgmt->sa;
event.rx_action.bssid = mgmt->bssid;
event.rx_action.category = mgmt->u.action.category;
event.rx_action.data = &mgmt->u.action.category + 1;
event.rx_action.len = frame + len - event.rx_action.data;
// EVENT_RX_ACTION帧代表ACTION帧
wpa_supplicant_event(drv->ctx, EVENT_RX_ACTION, &event);
} else {
event.rx_mgmt.frame = frame;
event.rx_mgmt.frame_len = len;
// EVENT_RX_MGMT代表其他类型的管理帧
wpa_supplicant_event(drv->ctx, EVENT_RX_MGMT, &event);
}
}
wpa_supplicant_event处理EVENT_RX_ACTION的内容比较丰富,不过对于P2P来
说,wpa_supplicant_event将调用wpas_p2p_rx_action,而wpas_p2p_rx_action又会
调用p2p_rx_action,所以此处直接看p2p_rx_action函数即可,其代码如下所示。
[–>p2p.c::p2p_rx_action]
void p2p_rx_action(struct p2p_data *p2p, const u8 *da, const u8 *sa,
const u8 *bssid, u8 category, const u8 *data, size_t len, int freq)
{
if (category == WLAN_ACTION_PUBLIC) {// 处理Public Action帧
p2p_rx_action_public(p2p, da, sa, bssid, data, len, freq);
return;
}
…// 参数检查
switch (data[0]) {// P2P规范使用的其他非Public类型的Action帧
case P2P_NOA:
break;
case P2P_PRESENCE_REQ:
p2p_process_presence_req(p2p, da, sa, data + 1, len - 1, freq);
break;
case P2P_PRESENCE_RESP:
p2p_process_presence_resp(p2p, da, sa, data + 1, len - 1);
break;
case P2P_GO_DISC_REQ:
p2p_process_go_disc_req(p2p, da, sa, data + 1, len - 1, freq);
break;

}
}
上述代码中专门处理Public Action帧的p2p_rx_action_public函数代码如下所示。
[–>p2p.c::p2p_rx_action_public]
static void p2p_rx_action_public(struct p2p_data *p2p, const u8 *da,
const u8 *sa, const u8 *bssid, const u8 *data,size_t len, int freq)
{

switch (data[0]) {
case WLAN_PA_VENDOR_SPECIFIC:// P2P Public Action满足此条件

p2p_rx_p2p_action(p2p, sa, data + 1, len - 1, freq);
break;
case WLAN_PA_GAS_INITIAL_REQ:
p2p_rx_gas_initial_req(p2p, sa, data + 1, len - 1, freq);
break;
…// 其他类型的Public Action帧处理
}
}
p2p_rx_p2p_action函数是P2P模块中Public Action帧得到分类处理的最后一关,其
代码如下所示。
[–>p2p.c::p2p_rx_p2p_action]
static void p2p_rx_p2p_action(struct p2p_data *p2p, const u8 *sa,
const u8 *data, size_t len, int rx_freq)
{
switch (data[0]) { // P2P支持的Public Action帧在此处得到分类和相应处理
case P2P_GO_NEG_REQ: // 处理GON Request帧
p2p_process_go_neg_req(p2p, sa, data + 1, len - 1, rx_freq);
break;
case P2P_GO_NEG_RESP: // 处理GON Response帧
p2p_process_go_neg_resp(p2p, sa, data + 1, len - 1, rx_freq);
break;
case P2P_GO_NEG_CONF: // 处理GON Confirmation帧
p2p_process_go_neg_conf(p2p, sa, data + 1, len - 1);
break;
case P2P_INVITATION_REQ: // 处理Invitation Request帧
p2p_process_invitation_req(p2p, sa, data + 1, len - 1,rx_freq);
break;
case P2P_INVITATION_RESP: // 处理Invitation Response帧
p2p_process_invitation_resp(p2p, sa, data + 1, len - 1);
break;
case P2P_PROV_DISC_REQ: // 处理PD Request帧
p2p_process_prov_disc_req(p2p, sa, data + 1, len - 1, rx_freq);
break;
case P2P_PROV_DISC_RESP: // 处理PD Response帧
p2p_process_prov_disc_resp(p2p, sa, data + 1, len - 1);
break;
case P2P_DEV_DISC_REQ: // 处理Device Discoverability Request帧
p2p_process_dev_disc_req(p2p, sa, data + 1, len - 1, rx_freq);
break;
case P2P_DEV_DISC_RESP: // 处理Device Discoverability Response帧
p2p_process_dev_disc_resp(p2p, sa, data + 1, len - 1);
break;
…// default语句
}
}
至此,Action帧的接收流程就介绍完了。整体而言,这部分代码难度不大,但是调用
函数却比较多。图7-35总结了这部分流程所涉及的一些重要函数。
图7-35 Action帧接收流程
由图7-35可知,p2p_rx_p2p_action为P2P Public Action帧处理逻辑的总入口,如果
后文分析时碰到其他类型的P2P Public Action帧,我们将直接转入该函数来分析。
3.PD Response帧处理流程
由上述的p2p_rx_p2p_action可知,PD Response帧对应的处理函数是
p2p_process_prov_disc_resp,其代码如下所示。
[–>p2p_pd.c::p2p_process_prov_disc_resp]
void p2p_process_prov_disc_resp(struct p2p_data *p2p, const u8 *sa,
const u8 *data, size_t len)
{
struct p2p_message msg; struct p2p_device dev; u16 report_config_methods = 0;
// 解析PD Response帧
if (p2p_parse(data, len, &msg)) return;
// 获取对应的P2P Device对象
dev = p2p_get_device(p2p, sa);

/

当前我们pending的action是PD,由于已经收到了PD Response,所以可以置pending_action_state
变量为P2P_NO_PENDING_ACTION。
/
if (p2p->pending_action_state == P2P_PENDING_PD) {
os_memset(p2p->pending_pd_devaddr, 0, ETH_ALEN);
p2p->pending_action_state = P2P_NO_PENDING_ACTION;
}
if (dev->dialog_token != msg.dialog_token)return;
if (p2p->user_initiated_pd &&
os_memcmp(p2p->pending_pd_devaddr, sa, ETH_ALEN) == 0)
p2p_reset_pending_pd(p2p);
/

如果所要求的WSC方法和PD Response返回的WSC方法不一致,则表明对端P2P设备不支持所要求的WSC方法。
*/
if (msg.wps_config_methods != dev->req_config_methods) {
// 调用wpas_prov_disc_fail,以处理PD失败的情况
// 不过WPAS中,该函数没有干什么有意义的事情
if (p2p->cfg->prov_disc_fail)
p2p->cfg->prov_disc_fail(p2p->cfg->cb_ctx, sa,P2P_PROV_DISC_REJECTED);
p2p_parse_free(&msg);
goto out;
}
report_config_methods = dev->req_config_methods;
dev->flags &= ~(P2P_DEV_PD_PEER_DISPLAY | P2P_DEV_PD_PEER_KEYPAD);

dev->wps_prov_info = msg.wps_config_methods;
p2p_parse_free(&msg);
out:
dev->req_config_methods = 0;
p2p->cfg->send_action_done(p2p->cfg->cb_ctx);// 请读者自行研究send_action_done函数
if (p2p->cfg->prov_disc_resp)// prov_disc_respz指向wpas_prov_disc_resp
p2p->cfg->prov_disc_resp(p2p->cfg->cb_ctx, sa,report_config_methods);
}
马上来看wpas_prov_disc_resp函数,其代码如下所示。
[–>p2p_supplicant.c::wpas_prov_disc_resp]
void wpas_prov_disc_resp(void *ctx, const u8 *peer, u16 config_methods)
{
struct wpa_supplicant wpa_s = ctx;
unsigned int generated_pin = 0;
/

pending_pd_before_join变量对应于这样一种场景:即GON已经完成,但WSC配置方法还没有确定。
在后文分析GON时,我们将见到这种场景。
/
if (wpa_s->pending_pd_before_join &&
(os_memcmp(peer, wpa_s->pending_join_dev_addr, ETH_ALEN) == 0 ||
os_memcmp(peer, wpa_s->pending_join_iface_addr, ETH_ALEN) == 0)) {
wpa_s->pending_pd_before_join = 0;
wpas_p2p_join_start(wpa_s);
return;
}
if (config_methods & WPS_CONFIG_DISPLAY)
wpas_prov_disc_local_keypad(wpa_s, peer, “”);
else if (config_methods & WPS_CONFIG_KEYPAD) {
generated_pin = wps_generate_pin();
wpas_prov_disc_local_display(wpa_s, peer, “”, generated_pin);
} else if (config_methods & WPS_CONFIG_PUSHBUTTON)
wpa_msg(wpa_s, MSG_INFO, P2P_EVENT_PROV_DISC_PBC_RESP MACSTR,MAC2STR(peer));

}
对于W SC PBC方法而言,wpa_msg将发送
P2P_EVENT_PROV_DISC_PBC_RESP(字符串,值为"P2P-PROV-DISC-PBC-
RESP")消息给客户端,这也触发了7.3.2节分析P2P_PROV_DISC_PBC_RSP_EVENT处
理流程中所描述的工作流程。
现在来看本章关于P2P的最后一到工序。
7.4.4 GO Negotiation流程分析
P2pStateMachine收到P2P_PROV_DISC_PBC_RSP_EVENT消息后,将在
ProvisionDiscoveryState中调用p2pConnectW ithPinDisplay,该函数内部将发送
P2P_CONNECT命令给W PAS。马上来看该命令的处理流程。
1.P2P_CONNECT处理流程
P2P_CONNECT命令的参数比较多,而本例中P2pStateMachine发送的命令格式如
下。
P2P_CONNECT 8a:32:9b:6c:d1:80 pbc go_intent=7
//其中,"8a:32:9b:6c:d1:80"代表对端P2P设备地址
//"pbc"指定了WSC配置方法为PBC,"go_intent=7"设置GO Intent值为7
P2P_CONNECT对应的处理函数为p2p_ctrl_connect,其代码如下所示。
[–>ctrl_iface.c::p2p_ctrl_connect]
static int p2p_ctrl_connect(struct wpa_supplicant
wpa_s,
 char *cmd,char *buf, size_t buflen)
{
u8 addr[ETH_ALEN]; char *pos, *pos2;
char *pin = NULL; enum p2p_wps_method wps_method;
int new_pin; int ret; int persistent_group;
int join; int auth;
int go_intent = -1; int freq = 0;
if (hwaddr_aton(cmd, addr)) return -1;
…// 参数处理,最终调用的函数为wpas_p2p_connect
new_pin = wpas_p2p_connect(wpa_s, addr, pin, wps_method,
persistent_group, join, auth, go_intent,freq);

os_memcpy(buf, “OK\n”, 3);
return 3;
}
wpas_p2p_connect的代码如下所示。
[–>p2p_supplicant.c::wpas_p2p_connect]
int wpas_p2p_connect(struct wpa_supplicant *wpa_s, const u8 *peer_addr,
const char *pin, enum p2p_wps_method wps_method,
int persistent_group, int join, int auth, int go_intent,int freq)
{
int force_freq = 0, oper_freq = 0;
u8 bssid[ETH_ALEN];
int ret = 0;
enum wpa_driver_if_type iftype;
const u8 if_addr;

if (go_intent < 0)go_intent = wpa_s->conf->p2p_go_intent;
wpa_s->p2p_wps_method = wps_method;
wpa_s->p2p_pin[0] = ‘\0’;
// 本例中,由于GON还未完成,GO角色未能确定,所以join为0
if (join) {…}
…// 频段设置
/

注意下面这个wpas_p2p_create_iface函数,它将判断是否需要创建一个新的virtual interface,还记得
7.4.1节介绍Driver Flags和重要数据结构时提到的WPA_DRIVER_FLAGS_P2P_MGMT_AND_NON_P2P
标志吗?就本例而言,wifi driver flags中包含了该标志,所以下面这个函数的返回值为1,表示需要单独创建
一个新的virtual interface供P2P使用。这个virtual interface的地址应该就是P2P Interface Address。
/
wpa_s->create_p2p_iface = wpas_p2p_create_iface(wpa_s);
if (wpa_s->create_p2p_iface) { // 本例满足此if条件
iftype = WPA_IF_P2P_GROUP; // 设置interface type
if (go_intent == 15) iftype = WPA_IF_P2P_GO; // 本例的go_intent为7
/

下面这个函数将创建此virtual interface,并获取其interface address。以Galaxy Note 2
为例。P2P Device Address是“92:18:7c:69:88:e2”,而P2P Interface Address是
“92:18:7c:69:08:e2”。
wpas_p2p_add_group_interface内部将调用driver_nl80211.c的wpa_driver_nl80211_if_add
函数。感兴趣的读者不妨自行研究。
*/
if (wpas_p2p_add_group_interface(wpa_s, iftype) < 0) return -1;
if_addr = wpa_s->pending_interface_addr;
} else
if_addr = wpa_s->own_addr;

// 下面这个函数内部将调用p2p_connect,我们将直接分析
if (wpas_p2p_start_go_neg(wpa_s, peer_addr, wps_method,
go_intent, if_addr, force_freq,persistent_group) < 0) {…}
return ret;
}
2.GON Request发送流程
来看p2p_connect函数,其代码如下所示。
[–>p2p.c::p2p_connect]
int p2p_connect(struct p2p_data *p2p, const u8 *peer_addr,
enum p2p_wps_method wps_method,
int go_intent, const u8 *own_interface_addr,
unsigned int force_freq,
int persistent_group)
{
struct p2p_device dev;
// 如果指定了工作频段,则需要判断是否支持该工作频段
if (p2p_prepare_channel(p2p, force_freq) < 0) return -1;
p2p->ssid_set = 0;
dev = p2p_get_device(p2p, peer_addr);
…// 设置dev的一些信息
p2p->go_intent = go_intent;
os_memcpy(p2p->intended_addr, own_interface_addr, ETH_ALEN);
// 如果P2P模块的状态不为P2P_IDLE,则先停止find工作
if (p2p->state != P2P_IDLE) p2p_stop_find(p2p);

dev->wps_method = wps_method;
dev->status = P2P_SC_SUCCESS;

if (p2p->p2p_scan_running) {
/

如果当前P2P还在扫描过程中,则设置start_after_scan为P2P_AFTER_SCAN_CONNECT标志,
当scan结束后,在扫描结果处理流程中,该标志将通知P2P进入connect处理流程。
*/
p2p->start_after_scan = P2P_AFTER_SCAN_CONNECT;
os_memcpy(p2p->after_scan_peer, peer_addr, ETH_ALEN);
return 0;
}
p2p->start_after_scan = P2P_AFTER_SCAN_NOTHING;
// 下面这个函数将发送GON Request帧,直接来看该函数
return p2p_connect_send(p2p, dev);
}
[–>p2p_go_neg.c::p2p_connect_send]
int p2p_connect_send(struct p2p_data *p2p, struct p2p_device *dev)
{
struct wpabuf *req; int freq;

req = p2p_build_go_neg_req(p2p, dev);
p2p_set_state(p2p, P2P_CONNECT); // 设置P2P模块的状态为P2P_CONNECT
// 设置pending_action_state为P2P_PENDING_GO_NEG_REQUEST
p2p->pending_action_state = P2P_PENDING_GO_NEG_REQUEST;
p2p->go_neg_peer = dev; // 设置GON对端设备
dev->flags |= P2P_DEV_WAIT_GO_NEG_RESPONSE;
dev->connect_reqs++;
#ifdef ANDROID_P2P
dev->go_neg_req_sent++;
#endif
// 发送GON Request帧
if (p2p_send_action(p2p, freq, dev->info.p2p_device_addr,
p2p->cfg->dev_addr, dev->info.p2p_device_addr,
wpabuf_head(req), wpabuf_len(req), 200) < 0) {…}

return 0;
}
至此,GON Request帧就将发送给对端P2P设备。图7-36描述了P2P_CONNECT命
令的处理流程。
图7-36 P2P_CONNECT命令处理流程
3.GON Response帧处理流程
根据前面对Action帧接收流程的分析可知,收到的GON Response帧将在
p2p_process_go_neg_resp函数中被处理。该函数的代码如下所示。
[–>p2p_go_neg.c::p2p_process_go_neg_resp]
void p2p_process_go_neg_resp(struct p2p_data *p2p, const u8 *sa,
const u8 *data, size_t len, int rx_freq)
{
struct p2p_device *dev; struct wpabuf conf; int go = -1;
struct p2p_message msg; u8 status = P2P_SC_SUCCESS;
int freq;
dev = p2p_get_device(p2p, sa);
// 解析GON Response帧
if (p2p_parse(data, len, &msg)) return;
…// 参数检查
/

下面这个函数将利用图7-13计算谁来扮演GO。返回值大于0,表示本机扮演GO,
返回-1表示双方都想成为GO,返回值为0,表示对端扮演GO。本例中,假设GO为本机设备。
*/
go = p2p_go_det(p2p->go_intent, *msg.go_intent);
…// 参数检查,内容比较繁杂,感兴趣的读者请自行研究
if (go) {…// 处理工作频段}
p2p_set_state(p2p, P2P_GO_NEG);// 设置P2P模块的状态为P2P_GO_NEG
p2p_clear_timeout(p2p);

fail:
// 构造GON Confirmation帧
conf = p2p_build_go_neg_conf(p2p, dev, msg.dialog_token, status,
msg.operating_channel, go);
p2p_parse_free(&msg);

if (status == P2P_SC_SUCCESS) {
p2p->pending_action_state = P2P_PENDING_GO_NEG_CONFIRM;
dev->go_state = go ? LOCAL_GO : REMOTE_GO;// 本机扮演GO
} else
p2p->pending_action_state = P2P_NO_PENDING_ACTION;

// 发送GON Confirmation帧
if (p2p_send_action(p2p, freq, sa, p2p->cfg->dev_addr, sa,
wpabuf_head(conf), wpabuf_len(conf), 200) < 0) {…}
wpabuf_free(conf);
}
p2p_process_go_neg_resp实际的代码比较复杂,建议初学者先了解上面代码所涉及
的大体流程。
当GON Confirmation帧发送出去后,wifi driver将向W PAS发送一个
NL80211_CMD_FRAME_TX_STATUS消息,而该消息将导致driver wrapper发送
EVENT_TX_STATUS消息给W PAS。下面我们直接来看EVENT_TX_STATUS的处理流
程。
4.EVENT_TX_STATUS处理流程
在events.c中,和P2P以及EVENT_TX_STATUS相关的处理函数是
offchannel_send_action_tx_status,该函数的代码如下所示。
[–>offchannel.c::offchannel_send_action_tx_status]
void offchannel_send_action_tx_status(struct wpa_supplicant *wpa_s, const u8 *dst,
const u8 data,size_t data_len, enum offchannel_send_action_result result)
{
…// 参数检查
wpabuf_free(wpa_s->pending_action_tx);
wpa_s->pending_action_tx = NULL;
/

注意下面这个pending_action_tx_status_cb参数,它是一个函数指针,P2P每次发送Action的时候,
都会设置该变量。其真实的函数为wpas_p2p_send_action_tx_status(在wpas_send_action
函数中设置)
*/
if (wpa_s->pending_action_tx_status_cb) {
wpa_s->pending_action_tx_status_cb( wpa_s, wpa_s->pending_action_freq,
wpa_s->pending_action_dst, wpa_s->pending_action_src,
wpa_s->pending_action_bssid, data, data_len, result);
}
}
来看wpas_p2p_send_action_tx_status,其代码如下所示。
[–>p2p_supplicant.c::wpas_p2p_send_action_tx_status]
static void wpas_p2p_send_action_tx_status(struct wpa_supplicant *wpa_s,
unsigned int freq,const u8 *dst, const u8 *src, const u8 *bssid,
const u8 *data, size_t data_len, enum offchannel_send_action_result result)
{
enum p2p_send_action_result res = P2P_SEND_ACTION_SUCCESS;

// 重要函数:p2p_send_action_cb
p2p_send_action_cb(wpa_s->global->p2p, freq, dst, src, bssid, res);

}
p2p_send_action_cb的代码如下所示。
[–>p2p.c::p2p_send_action_cb]
void p2p_send_action_cb(struct p2p_data *p2p, unsigned int freq, const u8 *dst,
const u8 *src, const u8 *bssid,enum p2p_send_action_result result)
{
enum p2p_pending_action_state state;
int success;
success = result == P2P_SEND_ACTION_SUCCESS;
// 读者还记得该变量的值吗?它应该是P2P_PENDING_GO_NEG_CONFIRM
state = p2p->pending_action_state;
p2p->pending_action_state = P2P_NO_PENDING_ACTION;
switch (state) {
case P2P_NO_PENDING_ACTION:
break;
case P2P_PENDING_GO_NEG_REQUEST:
// 读者可自行研究此函数。当发送完GON Request帧后,此函数也会被触发
p2p_go_neg_req_cb(p2p, success);
break;

case P2P_PENDING_GO_NEG_CONFIRM:
p2p_go_neg_conf_cb(p2p, result);// 分析这个函数
break;
…// 其他case处理
}
}
来看p2p_go_neg_conf_cb函数,代码如下所示。
[–>p2p.c::p2p_go_neg_conf_cb]
static void p2p_go_neg_conf_cb(struct p2p_data *p2p,
enum p2p_send_action_result result)
{
struct p2p_device dev;
/

每次收到TX Report后,都需要调用send_action_cb,其对应的真实函数是wpas_send_action_done。
*/
p2p->cfg->send_action_done(p2p->cfg->cb_ctx);

dev = p2p->go_neg_peer;
if (dev == NULL) return;
p2p_go_complete(p2p, dev);
}
p2p_go_complete函数比较简单,其代码如下所示。
[–>p2p.c::p2p_go_complete]
void p2p_go_complete(struct p2p_data *p2p, struct p2p_device *peer)
{
struct p2p_go_neg_results res; int go = peer->go_state == LOCAL_GO;
struct p2p_channels intersection; int freqs;
size_t i, j;
…// 设置频段等参数
p2p_set_state(p2p, P2P_PROVISIONING);// 进入Group Formation的Provisioning阶段
// go_neg_completed对应的函数是wpas_go_neg_completed
p2p->cfg->go_neg_completed(p2p->cfg->cb_ctx, &res);
}
[–>p2p_supplicant.c::wpas_go_neg_compeleted]
void wpas_go_neg_completed(void *ctx, struct p2p_go_neg_results *res)
{
struct wpa_supplicant wpa_s = ctx;

// 下面这个函数将导致P2pStateMachine收到P2P_GO_NEGOTIATION_SUCCESS_EVENT消息
wpa_msg(wpa_s, MSG_INFO, P2P_EVENT_GO_NEG_SUCCESS);
wpas_notify_p2p_go_neg_completed(wpa_s, res);
if (wpa_s->create_p2p_iface) {// 本例满足此if条件
/

再创建一个wpa_supplicant对象。感兴趣的读者可自行研究下面这个函数。其内部将调用4.4.3节
分析的wpa_supplicant_add_iface函数。
*/
struct wpa_supplicant *group_wpa_s =
wpas_p2p_init_group_interface(wpa_s, res->role_go);

// 如果本机扮演GO,则启动WSC Registrar功能,否则启动Enrollee功能
// 下面这两个函数留给读者自行分析
if (res->role_go) wpas_start_wps_go(group_wpa_s, res, 1);
else wpas_start_wps_enrollee(group_wpa_s, res);
} …
wpa_s->p2p_long_listen = 0;
eloop_cancel_timeout(wpas_p2p_long_listen_timeout, wpa_s, NULL);
eloop_cancel_timeout(wpas_p2p_group_formation_timeout, wpa_s, NULL);
// Group Formation的超时时间为15秒左右
eloop_register_timeout(15 + res->peer_config_timeout / 100,
(res->peer_config_timeout % 100) * 10000,
wpas_p2p_group_formation_timeout, wpa_s, NULL);
}
当Group Negotiation完成后,W PAS将新创建一个wpa_supplicant对象,它将用于
管理和操作专门用于P2P Group的virtual interface,此处有几点请读者注意。
·4.3.1节中介绍过,一个interface对应一个wpa_supplicant对象。
·此处新创建的wpa_supplicant对象用于GO,即扮演AP的角色,专门处理和P2P
Group相关的事情,其MAC地址为P2P Interface Address。
·之前使用的wpa_supplicant用于非P2P Group操作,其MAC地址为P2P Device
Address。
至此,整个EVENT_TX_STATUS处理流程就分析完毕了,其内容比较复杂。图7-37
整理了此过程中一些重要的函数调用。
注意,图7-37实际上描述的是GON Confirmation帧对应的EVENT_TX_STATUS处
理流程,和其他EVENT_TX_STATUS处理流程相比,前面7个函数调用都是一样的。
图7-37 EVENT_TX_STATUS处理流程
7.5 本章总结和参考资料说明
7.5.1 本章总结
本章对W i-Fi P2P进行了详细介绍,主要内容如下。
·P2P理论知识。从完整性来说,本章介绍的内容只是P2P规范中最基础的部分。但对
于初学者而言,这部分的难度也不算小。就笔者的学习经历而言,这部分内容需要反复琢磨
和研究,有时候还需要结合代码分析才能真正掌握其精髓。
·在学习完P2P理论知识后,对W ifiP2pSettings以及W ifiP2pService进行了介绍。
这部分内容比较简单,读者可轻松掌握相关知识。
·最后,对W PAS中的P2P模块及运行机制进行了介绍。就所涉及的知识而言,这些内
容并不复杂,但由于P2P以及和wifi driver在具体实现时有诸多考虑(例如off channel的
情况,TX Report的处理),所以其工作流程反倒显得比较烦琐。
最后,希望读者在本章的基础上,完成下列任务。
·通读P2P规范,了解Group Operation、Invitation、Device Discoverability以及
P2P Power Management相关知识。
·继续研究wpas_start_wps_go代码,掌握W PAS中W SC Registrar以及AP的工作流
程。
7.5.2 参考资料说明
1.P2P基础知识介绍
本章参考资料主要是W i-Fi P2P规范1.1版,可从http:/ / www.doc88.com/ p-
908280242988.html下载协议全文。
2.P2P架构介绍
[1] “W i-Fi P2P"的第2章"Architectural Overview”
[2] “W i-Fi P2P"的3.2节"P2P Group Operation”
[3] “W i-Fi P2P"的3.3节"P2P Power Management”
3.P2P Device Discovery和Group Formation
[4] “W i-Fi P2P"的3.1节"P2P Discovery”
[5] Part11:W ireless LAN Medium Access Control(MAC)and Phsyical
Layer(PHY)Specifications附录J"Country information element and regulatory
classes”
说明:该文档下载地址为
http:/ / download.csdn.net/ download/ s_bird0529/ 2553723。附录J详细描述了国家码和
管制信息方面的内容。
[6] “W i-Fi P2P"的4.2节"Management Frames”
[7] “802.11-2012"的8.4.1.11节"Action Field”
说明:该节介绍了Action帧Category字段的取值情况。
[8] “W i-Fi P2P"的4.1节"P2P Information Elements”
[9] “W i-Fi P2P"附录A"P2P State Machine”

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入理解Wi-Fi P2P 的相关文章

  • 网络地址转换的类型及特点

    1 网络地址转换的类型及特点 NAT有三种类型 静态NAT StaticNAT 动态地址NAT PooledNAT 网络地址端口转换NAPT Port LevelNAT 静态NAT 指将内部网络的私有IP地址转换为公有IP地址 IP地址对是
  • activemq结合mqtt发送p2p消息

    实现思路 所有用户订阅一个主题 当服务器端发起推送时使用jms协议发送消息到主题 并设置附带属性为目标用户的clientId 对该主题进行自定义分发策略 1 下载mqtt源码 自行下载 本案例以5 5 10为例 2 自定义分发策略 添加一个
  • 免费获取知网文献----浙江图书馆+支付宝

    使用支付宝办理浙江图书馆读者证 在支付宝搜索 浙江图书馆 并进入其生活号 开通借阅服务 办理读者证 使用读者证号登录浙江图书馆官网 通过以上步骤成功办理读者证后 会获取一个读者证号 在支付宝 浙江图书馆 生活号的首页或个人中心可以看到 使用
  • 数据结构练习题-算法设计题-线性表

    算法设计题 1 将两个递增的有序链表合并为一个递增的有序链表 要求结果链表仍使用原来两个链表的存储空间 不另外占用其它的存储空间 表中不允许有重复的数据 题目分析 合并后的新表使用头指针Lc指向 pa和pb分别是链表La和Lb的工作指针 初
  • T系接口源数据格式

    item apiStack name esi value endpoint mode android osVersion 9 26 0 protocolVersion 3 0 ultronage true data dinamic TB d
  • word将一个文档的样式导入到另一个文档

    一 背景 在word中编辑文档时 经常需要定义一个样式给特定格式的文本使用 如标题1 标题2等 而有时需要在一个新文档A中使用一个旧文档B中定义好的样式 二 操作步骤 1 打开旧文档B 选择上方标签栏的 样式 gt 管理样式 如图 2 在弹
  • openstack镜像的管理与使用

    1 创建项目和用户 1 1 创建项目和用户 用SSH工具连接控制节点 这里是10 10 83 3 使用admin用户权限 执行如下命令 root admin openrc 1 2创建一个test项目 openstack project cr
  • 2E丑数(DP)

    include
  • NoteExpress安装时问题解决

    每次安装软件我都不能一次性成功 这次遇见的是NoteExpress和Word权限不一致的问题 版本 win10 office2019 网上有很多方法 其中CSDN博主 令令狐大侠 总结郭一篇 原文链接 https blog csdn net
  • centOS 安装 elasticsearch 7.0.0和kibana7.0.0_单机版

    cd opt wget wget https artifacts elastic co downloads elasticsearch elasticsearch 7 0 0 linux x86 64 tar gz tar xzvf ela
  • 1093: 数1的个数

    存限制 128 MB 题目描述 给定一个十进制正整数n 1 n 10000 写下从1到n的所有整数 然后数一下其中出现的数字 1 的个数 例如当n 2时 写下1 2 这样只出现了1个 1 当n 12时 写下1 2 3 4 5 6 7 8 9
  • PLC控制电动机的顺序启动逆序停止

    一 PLC控制电动机延迟启动正转和反转 实验要求 当按下电动机的正转启动按钮时 电动机需要延迟30秒后 电动机才会正转启动工作 当按下反转按钮时 电动机需要延迟20秒后 电动机才会反转启动工作 当按下停止按钮时电动机立刻停止工作 PLC I
  • 使用七牛云进行文件上传

    目录 一 七牛云入门测试 1 注册七牛云账号 完成后选择对象存储 2 在里面创建空间 一个空间相当于一个文件夹 就是将对象上传到的地方 3 查看个人秘钥 注册完成账号后 会有一个秘钥 上传文件的时候进行授权和认证 4 文件上传测试 二 封装
  • 是否可以连接两个或多个 WiFi Direct 组?

    我目前正在为我正在进行的一个项目尝试 WiFi Direct WiFiP2p 并想知道是否可以在组之间创建桥梁 从而将它们连接在一起 基于白皮书由 WiFi 联盟发布 这应该是可能的 尽管 P2P 规范没有描述此功能的机制 实施是特定于供应
  • C++ Winsock P2P

    Scenario 有没有人有任何使用 Winsock 在 C 中进行点对点 p2p 网络的好例子 这是我对特别需要使用这项技术的客户的要求 天知道为什么 我需要确定这是否可行 任何帮助将不胜感激 EDIT 我想避免使用库 以便我可以理解底层
  • 实现 p2p 消息广播网络的最新技术是什么?

    我知道快速谷歌可以得到大量的结果 并且关于这个主题的文献非常丰富 而这正是问题所在 在众多可能的解决方案中 我不确定哪一个是满足我的特定需求的最佳 最新的选择 我正在尝试在互联网上实现一个 p2p 网络 其唯一的功能是将消息广播到在线节点
  • WebRTC:匹配最近的同行

    给定一个公共 IP 地址 对等点 A 和许多其他公共 IP 地址 IPv4 和 IPv6 地址的混合 列表 将对等点 A 的 IP 地址匹配的最简单方法是什么 n最近的对等点 而无需让对等点手动相互 ping 通以进行延迟基准测试 我认为使
  • WCF 是否支持点对点实现?

    我正在尝试在 LAN 内实现点对点消息传递和文件共享实用程序 那么 WCF 支持 p2p 吗 有人尝试过通过 WCF 进行文件共享吗 是的 它确实 请参见如何在对等网络中设计状态共享 http msdn microsoft com en u
  • P2P网络游戏/应用程序:类似“战网”匹配服务器的不错选择

    我正在制作一个网络游戏 1v1 游戏中是 p2p 不需要游戏服务器 然而 为了让玩家能够 找到彼此 而不需要在另一种媒介中协调并输入IP地址 类似于网络游戏的现代时代 我需要有一个协调 匹配服务器 我无法使用常规网络托管 因为 客户端将使用
  • 智能手机可以通过 3G/4G 进行点对点通信吗?

    我正在尝试编写一个应用程序 将数据从一个 Android 设备传输到另一个 Android 设备 但这些设备很可能位于城市 州或国家的不同部分 直接的方法是拥有一台中央服务器 或任何类型的服务器 但我试图避免使用中央服务器 我试图传递的数据

随机推荐