精华内容
下载资源
问答
  • Android Wifi 千次阅读
    2021-12-02 19:23:30

    Android

    基于Linux的系统,从上而下分为应用层,应用框架层、函数库、虚拟机、内核层

    Netd

    网络管理和控制的后台daemon(守护程序),主要有三个方面的功能:

    • 防火墙设置、网络地址转换、带宽控制、无线网卡软接入点、网络设备绑定
    • DNS的缓存与管理
    • 网络服务搜索

    netd位于框架层与内核层之间,其main函数主要有四个成员:

    NetLinkManager

    网络链接管理器,简称NM,用于创建并接收来自Kernel(内核层)的UEvent消息,核心代码位于start函数中。start函数注册了三个用于接收UEvent的socket

    • KOBJECT:/sys/class/net下相关模块的加载与卸载
    • ROUTE:网络链路的断开与链接
    • NFlOG:带宽控制

    CommandListener

    命令监听器,简称CL,接收来自框架层NetWorkManageService的命令,定义了是11个Command类和10个controller类

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J3JPFJQQ-1638444199769)(…/AppData/Roaming/Typora/typora-user-images/image-20211125152843447.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ksY40KCU-1638444199771)(…/AppData/Roaming/Typora/typora-user-images/image-20211125154241104.png)]

    InterfaceCmd

    InterfaceCmd用来管理和控制系统中的网络设备

    CL构造函数利用iptables等工具创建较多的Chain和Rule,以及对某些命令控制对象初始化

    CL创建后filter表包含五部分内容:

    • target:目标名
    • prot:protocol 协议
    • opt:选项
    • source:数据包的源
    • destination:目标地址

    bw_INPUT、bw_OUTPUT、bw_FORWARD Chain用于带宽(Bandwidth)控制

    fw_INPUT、fw_OUTPUT、fw_FORWARD用于防火墙(Firewall)控制

    narctrl_FROWARD用于网络地址转换(NAT)

    oem_fwd、oem_out用于OEM厂商自定义的控制。

    ndc测试工具:

    • 监视Netd中发生的事情
    • 支持命令行发命令给Netd执行

    PPP(点对点协议),为同等单元之间传递数据包设计的链路层协议,提供全双工,按顺序传递数据包。主要包含三个部分

    • 链路控制协议(LCP):负责创建、维护或终止一次物理连接
    • 网络控制协议(NCP):包含一簇协议,负责解决物理连接上运行什么网络协议,以及解决上层网络协议发生的问题。
    • 认证协议:常用的有口令验证协议(PAP)、挑战握手验证协议。使用时,客户端会将自己的身份发送给远端的接入服务器。期间将使用认证协议避免第三方窃取数据或冒充远程客户接管与客户的连接。

    pppd是运行ppp协议的后台进程。他和kernel中的ppp驱动联动,以完成在直连线路(DSL、拨号网络)上建立IP通信链路的工作

    DnsProxyListener

    域名解析socketAPI:

    getaddrinfo:根据指定host名或service获得对应ip地址

    getnameinfo:根据指定ip地址获得对应的host或service名称

    域名代理监听,简称DPL,定义了两个命令:

    • GetAddrInfoCmd:和Bionic C库的getaddrinfo函数对应
    • GetHostByAddrCmd:和Bionic C库的gethostbyaddr函数对应

    MDnsSdListener

    组播域名服务,对应的框架层服务为NsdService(NetworkServiecDiscover),运行过程分三步:

    • Netd创建MDnsSdListener对象,其内部会创建Monitor对象,Monitor对象启动一个线程用于和mdnsd通信,并接收来自handler的请求
    • NsdServices启动完毕后向MDnsSdListener发送“start-service”命令
    • NsdService响应应用程序的请求,向MdnsSdListener发送其他命令,例如discover等,Monitor最终处理这些请求。

    Linux中 iptables tc ip命令

    iptables:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T8wojR9q-1638444199773)(…/AppData/Roaming/Typora/typora-user-images/image-20211125110751833.png)]

    网络管理工具 iptables Target 和常用参数

    iptables中的rule有四个默认定义的Target

    • ACCEPT:接收数据包
    • DROP:直接丢弃数据包。没有任何数据反馈给数据源端
    • RETURN:返回到调用chain,略过后续的rule处理
    • QUEUE:数据返回到用户空间去处理

    设置防火墙:

    iptables -t filter -A input -s 192.168.1.108 -j DROP

    仅拦截协议为tcp的数据包

    iptables -t filter -A input -p tcp -s 192.168.1.108 -j DROP

    iptables仅支持IPV4

    tc命令

    Traffic Control 流量空限,通过建立数据包队列(Queue),并控制各个队列中 数据包的发送方式来实现

    ip命令

    网络管理工具,功能:

    • 可代替ipconfig命令,通过ip工具可管理系统中的网络接口,包括配置并查看网络接口情况
    • 可替代route命令,即ip工具支持主机路由,网络路由,网管参数等
    • 可替代arp命令,即ip工具支持查看、修改、管理系统的ARP缓存
      • ARP:地址解析协议

    WiFi

    Wireless Fidelity

    Test Plan(Wi-Fi测试方案)参考了IEEE802.11规范,并拓展了802.11内容

    IEEE802 局域网、城域网标准

    IEEE802.11 无线局域网的介质访问控制协议以及物理层技术规范

    计算机网络OSI七层模型

    应用层

    常见协议:HTTP、HTTPS、FTP、SMTP等。其数据单位为APDU

    表示层

    为不同客户端提供数据和信息的语法转换、压缩解压缩、加密解密等服务。数据单位为PPDU

    会话层

    为通信双方制定通信方式,创建和注销会话(双方通信)等。常见协议有ZIP、AppleTalk、SCP。数据单位为SPDU

    传输层

    控制数据流量,同时能进行调试及错误处理,确保通信顺利。发送端为数据分组加上序号,接收端将分组重组为有用的数据或文件。常见协议有TCP、UDP等。数据单位为TPDU

    网络层

    为数据传输的目的寻址,然后再选择一个传输数据最佳路线。网络层数据单位为Package或Datagram。常见设备有路由器等,常见协议有IP、IPv6

    数据链路层

    在物理层提供比特流的基础上,建立相邻节点之间的数据链路。通过差错控制提供数据帧(Frame)在信道上无差错的传输。数据链路层在不可靠的物理介质上提供可靠的传输。改层的作用包括物理地址寻址、数据的成帧、流量控制、数据检错、重发等。数据链路层的单位为Frame(帧),常见设备有二层交换机、网桥等。

    物理层

    数据单位为bit,定义了设备机械、电气、功能和过程等方面的特性。建立、维护、拆除物理链路连接。

    TCP/IP四层协议 传输控制协议/网际协议

    • 应用层
      • 应用层
      • 表示层
      • 会话层
    • 传输层
      • 传输层
    • 网络层
      • 网络层
    • 网络接口层
      • 数据链路层
      • 物理层

    数据链路层

    数据链路层分为LLC子层和MAC子层

    媒体访问控制子层(MAC)

    解决局域网(LAN)共用信道的使用发生竞争时,如何分配信道的使用权。

    无线局域网的介质访问控制协议只涉及MAC层,主要采用CSMA/CA(载波监听多路访问/冲突避免机制)

    两种方式:延迟发送:当无线链路被占用时,设备随机为每一帧选择一段退避(backoff)时间,减少冲突发生。

    ​ RTS和CTS握手:

    MAC层为上层提供MACService,MACService内部包含数个Entity。Entity封装了一组功能模块,MAC Entity对上层提供MAC Service。Entity可以相互调用来完成某个功能。

    MAC Service 为上层提供服务时必须经由MAC Service access Point来访问。

    逻辑链路控制子层(LLC)

    实现了两个站点之间帧的交换,实现端到端(源到目的),无差错的帧传输和应答功能及流量控制功能。

    802.11协议

    规范定义了无线局域网中MAC层和PHY层的技术标准

    802.11组件

    • 物理组件:820.11无线网络包含四种主要物理组件
      • WM(无线媒介):能传送无线MAC帧数据的物理层,包括红外和射频两种,使用最多的是射频物理层
      • STA(工作站):指携带无线网卡的设备
      • AP(Access Point,接入点):分布式STA
      • DS(分布式系统):
    • 无线网络的构建:无线网络由物理组件构成,基本服务集(BSS)是整个无线网络的基本构成组件,它有两种类型
      • 独立型BBS:不需要AP参与参与,各STA之间可直接交互
      • 基础结构型BBS:所有的STA之间交互必须经过AP,AP是BSS的中控台。
    • 拓展服务集ESS:几个BSS联合工作构建更大的无线网络

    802.11 Service

    从模块来看,802.11中的Service分为两大类别

    • SS(Station Service):他是STA应该具有的功能
    • DSS:从逻辑上指明DS应该具有的功能

    数据传输相关服务

    无线网络数据传输的数据是无线MAC帧数据。

    • Distribution Service(DS,分布式服务)
    • Integration Service(IS,整合服务)

    无线网络三个重要的服务

    • 数据传输服务:无线网络的一个主要功能
    • 安全方面的服务:
    • 无线电测量服务:用于无线网络的组建。

    802.11MAC服务和帧

    MAC层定义了一些Service用于为上层LLC提供服务

    • request:供LLC层发送数据
    • indication:通知LLC层进行数据接收
    • STATUS.indaction:通知LLC层自己(MAC)层的状态
    MAC帧

    MAC帧由三个基本域组成

    • MAC Header:

      • 控制帧(Frame Control):共2字节16位

        • Protocol Version:版本号

        • Type:MAC的类型:

          • 数据帧:一般Frame Body最大长度为2312字节。

          • 控制帧:控制帧的作用包括协助数据帧的传递、管理无线媒介第访问等

            • RTS:申请无线媒介的使用时间。RTS的MAC标头由4个字段字段构成:

              • Frame Control(帧控制)
              • Duration(持续时间)
              • Address1字段:Receiver Address(接收端地址)
              • Address2字段:Transmitter Address(发送端地址)
            • CTS:用于回复RTS。CTS的MAC标头由3个字段构成

              • Frame Control(帧控制)
              • Duration(持续时间)
              • Address1字段:Receiver Address(接收端地址)
            • ACK:MAC以及任何数据的传输都需要得到肯定确认。这些数据包括普通的数据传输、RTS/CTS交换之前帧以及分片帧

            • PS-POLL:该控制帧被STA用于从AP中获取因省电模式而缓存的数据。其中AID的值是STA和AP关联时,由AP赋给该STA的

          • 管理帧:

            • Beacon(信标)帧:AP定时发送Beacon帧用来声明某个网络。必须包含

            • Timestamp:时间戳信息,定长字段,8字节,用于同步BSS中的STA,时间单位为微秒

            • Beacon Interval:宣布无线网络的存在

            • Capability:占2字节,该字段用于宣告此网络具备何种功能。

            • SSID:网络名,用字符串表达

            • Association Request/Response(关联请求/回复帧):当STA需要关联某个AP时,将发送此帧。

              • Capability:AP将检查该字段用于判断STA是否满足要求
              • Listen Interval:AP将根据该值分配PS时所需的缓冲
              • SSID:AP将检查SSID是否为自己所在的网络
              • Supported Rates:AP将检查是否满足STA的传输速率。
            • Probe Request/Response(探测请求/回复帧):用于搜索周围的无线网络必须包含:

              • SSID:要搜索的网络名。如果SSID长度为0,代表搜索周围的所有无线网络
              • Supported Rates:STA支持的传输速率,如果AP支持这些速率,他会允许STA加入网络。
              • Extended Supported Rates:拓展Supported Rates
            • Authentication/Deauthentication(认证/取消认证)帧:用于身份验证

              管理帧的MAC Header包含6个域,具体的管理信息数据将由Frame Body携带,管理信息数据又大体可分为两种类型:

            定长字段:长度固定的信息,Fixed Field

            • Authentication Alogorithm Number:占两字节,代表认证过程所使用的认证类型。取值如下

              • 0:代表开放系统身份认证(Open System Authentication)
              • 1:共享密钥身份认证(Shared Key Authentication)
              • 2:快速BBS切换(Fast BSS Transition)。
              • 3:SAE(Simultaneous Authentication of Equals)两个STA互相认证的方法,常用于Mesh BSS网络。
              • 65535:代表厂商自定义算法。
            • Beacon Interval field:占2字节。每隔一段时间AP就会发出Beacon信号用来宣布无线网络的存在。该信号包含了BSS参数等重要信息。所以STA必须要监听Beacon信号。Beacon Interval field字段表示Beacon信号之间的间隔时间,其单位为Time Units(TU,一个TU为1024微妙。)

            • Capability Information(性能信息):占2字节,一般通过Beacon帧、Probe Request和Response帧携带它。该字段用于宣告此网络具备何种功能。

            • Current AP Address:6字节,表示当前和STA关联的AP的MAC地址。作用是便于关联和重新关联操作

            • Listen Interval:2字节,和省电模式有关。作用是告知AP,STA进入PS模式后,每隔多长时间它会醒来接收Beacon帧。AP可根据该值为STA设置对应的缓存大小,该值越长,对应的缓冲也相应较大。

            • Association ID(AID):2字节。 STA和AP关联后,AP会为其分配一个AssociationID用于后续的管理和控制。最高2位永远为1,取值范围1~2007。

            • Reason Code:2字节,通知关联失败的原因。0->保留,1->未指定,2->前一个身份验证以失败,3->STA离开BSS或者ESS导致验证失败,4->STA不活跃时间超时故取消关联。5->AP没有资源再处理新的STA故取消关联

            • Status Code:2字节,用于反馈某次操作的处理结果。 0->成功,

            信息元素(IE):长度不固定的信息。标准结构由Element ID、 Length、Information构成。

        • SubType:子类型

        • ToDS:只用在数据类型的帧中

        • FromDS:只用在数据类型的帧中

          • ToDS:0,FromDS=0IBSS:一个STA发送到另一个STA ;BSS:一个非AP的STA发送给另一个非AP的STA
            ToDS:1,FromDS=0发送给DS或AP的STA的MAC帧,发给DS必须进过AP中转
            ToDS:0,FromDS=1来自DS或AP的数据帧
            ToDS:1,FromDS=1
        • More Fragments:是否分片,只支持数据帧和管理帧

        • Retry:是否重传包,1->重传

        • PowerManagement:表明发送该帧的STA处于活跃模式还是省电模式

          • AP不缓存的管理帧,PM无用
          • 对于AP发送的帧,PM无用
          • STA发送给未与之关联的AP,PM无用
          • 1-> STA进入PS(Power Save)状态,
        • More Data:和省电模式相关。0 ->STA 已近接收完数据帧

        • Protected Frame :数据是否加密

        • Order:接收端必须按顺序处理该帧

      • 时长(Duration/ID ) :2字节16位,根据Type 和SubType不同而不同,大体代表ID和duration

        • PS-POLL帧:表示AID的值
        • 其它帧,代表离 下一帧到来还有多长时间,单位是微秒。
      • 地址(Address)

        Mac地址:6字节十六进制表示,组成包括两部分:

        • 0-23位:组织唯一标识符,后24位:网卡唯一编号
        • 第48位:表示地址是单播还是组播: 1->组播地址,0->单播,MAC地址全为1,则该MAC为广播地址。
        • 第47位表示该MAC地址是全球唯一还是本地唯一。1->本地唯一

        802.11MAC帧头部包含四个Address域

        • BSSID
          • 对于BSS,即为AP地址
          • 对于IBSS:本地唯一MAC地址,46位随机数产生,G/L位为1,本地唯一;U/M位为0,单播,
        • 目的地址(Destination Address,DA):用来描述MAC数据包的最终接收者,可以是单播或者组播地址
        • 源地址(Source Address,SA):用来描述最初发出MAC的STA地址,一般情况是单播
        • 发送STA地址(Transmitter Address,TA):将MAC数据包发送到WM(无线媒介)的ASTA地址
        • 接收STA地址(Receiver Address,RA):用于描述接收MAC帧数据的
    • Frame Body :帧域 长度可变

    • FCS(Frame Check Sequence,帧校验序列 ):保障帧数据的完整性

    • 802.11上层协议封装:802.11使用LLC层来封装上层协议。当Ethernet(以太网)帧要在无线网络上传输时,必须先将其转换成LLC帧,由RFC 1042规定。它主要在MAC Header和Type之间增加了4个字段,统称SNAP(子网访问协议)。

      • DSAP(目标服务接入点)
      • SSAP(原服务接入点)
      • Control(控制字段,未编号信息)
      • OUI

    802.11MAC管理实体

    MAC子层中专门用于管理的Entity(实体):MLME,对外提供接口:MLME_SAP

    Scan:用于扫描周围的无线网络,常用参数
    • Request:Scan.request用于扫描周围的无线网络

      • BSSType
      • BSSID:类型为MAC地址。可以是某个指定的BSSID或广播BSSID
      • SSID:类型为字符串。0~32字节长,如果长度为0,则为wild ssid。
      • ScanType:类型为枚举,可取值为ACTIVE(主动)、PASSIVE(被动)
      • ProbeDelay:整型,单位为微妙,用于ACTIVE模式扫描
      • ChannelList:类型为有序整数列表(Ordered set of integers) ,扫描时使用。
      • MinChannelTime和MaxChannelTime:类型均为整型。用于指示扫描过程中在每个信道上等待的最小和最长时间。单位为TU
    • 两种扫描模式:

      • ACTIVE模式:STA在每个Channel(信道)上都会发出Probe Request帧用来搜索某个网络。STA首先调整到某个网络,然后等待来帧指示(表明该信道有人使用)或超时时间Probedelay。两个条件满足任意一个,STA都将发送Probe Request帧,然后STA在该信道上等待最少时间(信道一直空闲),最长MaxChannelTime时间
      • PASSIVE模式:STA不发任何信号,只是在ChannelList中各个信道不间断切换并等待Beacon帧。
    • confirm:用于通知扫描结果

      • BSSDescriptionSet:类型为BSSDescription列表
      • ResultCode:类型为枚举,可取值为SUCCESS和NOT_SUPPORTED

    ​ 获取周围的无线网络后,STA可以选择加入(join)其中的一个BSS

    Authenticate:关联某个AP前,用于STA的身份验证。

    ​ 关联到某个AP之前,STA必须通过身份验证。

    • MLME-Authenticate.request:STA A向AP B发起身份验证请求

      • PeerSTAAddress:类型为MAC地址,代表对端STA的地址
      • AuthenticationType:类型为枚举,可取值有OPEN_SYSTEN、SHARED_KEY、FAST_BSS_TRANSITION和SAE。
      • AuthenticateFailureTimeout:类型为整型代表认证超时时间,单位为TU。
      • ResultCode:代表认证处理结果
    • MLME-Authenticate.confirm: STA A收到来自AP B的身份验证处理结果

    • MLME-Authenticate.indication:AP B收到来自STA A的身份验证处理请求

    • MLME-Authenticate.response:AP B向STA A发送身份验证处理结果

    Associate:关联某个AP。成功后,STA就正式加入无线网络了
    • PeerSTAAddress:相应Association请求的STA的MAC地址,即AP地址
    • AssociateFailureTimeout:整型,代表关联超时时间,单位为TU
    • CapabilityInformation:指定AP的性能信息
    • ListenInterval:用于告知AP,STA进入省电模式之后,监听Beacon的间隔时间
    • RSN:类型为RSNE,指示STA选设置的安全方面的信息
    • ResultCode:AP返回处理的结果
    • AssociationID:AP返回的关联ID
    • SupportedRates:AP返回的所支持的传输速率列表。
    STA状态切换

    MAC帧一共有三种类别、CLASS1、CLASS2、CLASS3,各自包含不同的数据帧

    类别控制帧管理帧数据帧
    Class1RTS、CTS、ACKProbe Request/Response、Beacon、Authentication、Deauthentication、Public Action
    Class2Association Request/Response Reassociation Request/Response
    ClassPS-POLL等除Class和Class2包含的管理帧之外的其他管理帧BSS中所有数据帧

    STA从最初到最终经历四个阶段

    • State1:未认证,未关联。
    • State2:已认证,未关联
    • State3:已认证,已关联,未通过RSN(Robust Security Network,强健安全网络)认证的状态。未通过RSN认证,只能发送处理认证第数据帧,即4-Way Handshake(四次握手)帧。
    • State4:已认证,已关联, 成功后,STA进入State4.此时他完全加入无线网络,所有数据帧就能正常传输。

    无线网络安全

    无线网络主要有三个保护点。

    • 数据的完整性(Integrity):用于检查数据在传输过程中是否被修改
    • 数据的机密性(Confidentiality):用于确保数据不会被泄露
    • 身份验证和访问控制(Authentication and Access Control):用于检查受访者的身份。

    无线网络安全技术发展历程:WEP(有线等效加密)------>WPA(Wi-Fi Protected Access)--------->RSN(Robust Security Network,强健安全网络),WFA将RSN称之为WPA2

    WEP

    WEP身份验证:

    • 开放身份验证(Open System Authentication):只有通过开放式验证才能使用RSNA验证
    • 共享密钥身份验证(Shared Key Authentication):共享的密码,shared Key验证的方法包括四次帧交互。
      • STA发送Authentication请求给AP
      • AP接收到请求后,通过Authentication Response 发送一个质询明文(Challenge Text,128字节)给STA
      • STA取出Challenge Text,通过WEP加密方法利用自己设置的密钥对质文进行加密,然后再次发送Authentication Request 给AP。
      • AP收到第二次Authentication Request帧后,会利用AP的密钥进行解密,同时还要验证数据完整性。如果ICV(完整性校验值)正确,并解密之后的译文等于之前发送的质询文,则可知STA拥有正确的密码,故STA身份验证通过。
      • STA返回验证处理结果给STA。如果成功,STA后续将通过Association请求加入该无线网络
    可拓展身份验证协议(EAP)

    EAP是一种简单的封装方式,可以运行于任何的数据链路层以及使用各种身份验证的方式。

    EAP的封包格式:

    • Code(类型代码):1个字节,代表EAP封包的类型。封包的Data(数据)字段必须通过此字段来解析。
    • Identifier(标识符):Identifier(标识符)的长度为1字节。 内容为1个无符号整数,用来匹配请求与响应。重传时会使用相同的Identifier number(标识符编号),新的传送使用新的Identifier number。
    • Length(长度):Length(长度)本身占2字节。它记载了整个封包的总字节数,包括Code、Identifier、Length以及Data这4个字段。
    • Data(数据):最后一个是Data(数据)字段,其长度不定,取决于封包的类型,Data也可能不占用任何字节。Data字段如何解析完全取决于Code字段的值

    EAP的请求与响应

    EAP的交换操作由请求(Request)与响应(Response)构成。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bro0wiF2-1638444199773)(%E5%AD%A6%E4%B9%A0.assets/image-20211201100147030-16383301375571.png)]

    Code字段的值为1代表请求,2代表响应。Data字段用来携带请求与响应所使用的数据。每个Data字段会携带一种数据,可再分为Type Identifier code(类型标识符)以及associated data(关联数据):

    • Type(类型):Type字段的长度为1个字节,代表请求或响应类型。每个封包只能使用一种类型。响应的Type字段与其对应的请求一致,当无法接受某个请求时,对方可以发送一个NAK来提议使用不同的类型。大于或等于4的Type字段值代表身份验证方式。
    • Type-Data(类型-数据)
    • Type-Data(类型-数据)字段长度不定,必须根据每种类型规则加以诠释。
      • 类型代码1:Identity(身份)
      • 类型代码2:Notification(通知)
      • 类型代码3:NAK(否定确认)

    Transport Layer Security(传输层安全,简称TLS)

    Secure Socket Layer(安全套接层,简称SSL)

    WPA
    • WPA 采用MIC(Message Integrity Check,消息完整性校验)算法用于替代WEP中的CRC算法,取名为Michael
    • 采用TKIP(Temp Key Integrity Protocol,临时密钥完整性协议)用于为每一个MAC帧生成不同的Key。这种为每帧都生成单独密钥的过程称为密钥混合(Key Mixing。)
    RSN数据加密及完整性校验
    • TKIP(Temp Key Integrity Protocol,临时密钥完整性协议)加密方式
    • CCMP (Counter Mode with CBC-MAC Protocol,计数器模块及密码块链消息认证码协议)

    PMK(Pairwise Master Key,成对主密钥):

    PTK(PairwiseTransient Key,成对传输密钥):用于单播数据帧的加密和解密

    GTK(Group Temporal Key,组临时密钥):用于组播数据帧和广播数据帧的加解密

    MIC(Message Integrity Check,数据及消息校验码)

    PTK包含四个部分:

    • KCK:用来在EAPOL4-way handshake时校验完整性
    • KEK:用于EAPOL4-way handshake的3/4阶段加密GTK
    • TK用于加密单播数据
    • MIC:用于数据传输时TKIP方式的Michael完整性校验
    四次握手过程:

    **目的:**为确保无线传输数据的安全性和完整性,通过四次握手协商密钥产生PTK 、GTK

    过程:

    • 1/4:Authenticator---->Supplicant

      Authenticator(认证者)将ANonce发送给Supplicant(请求者)。Supplicant收到M1后,就有了生成PTK的所有元素

    • 2/4:Supplicant------>Authenticator

      Supplicant计算出PTK,将SNonce和自己的MAC地址发送给Authenticator。同时从2/4开始每个报文都会有MIC

    • 3/4:Authenticator---->Supplicant

      Autenticator向Supplicant证明自己是有效的,同样有MIC加入其中

    • 4/4:Supplicant------>Authenticator

      Supplicant向Authenticator发送ACK,确认PTK已经装好,后面的数据可以进行加密。

      **WPA 、WPA2 在4次握手上的区别:**WPA的GTK在4次握手完成后进行安装,WPA2在4次握手的过程中就进行了安装

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9lFqoheC-1638444199774)(%E5%AD%A6%E4%B9%A0.assets/image-20211201174139700-16383517070781.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Smhcr5Ax-1638444199775)(%E5%AD%A6%E4%B9%A0.assets/image-20211201180120959.png)]

    WPA2四握手协议

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LBnEqTYY-1638444199776)(%E5%AD%A6%E4%B9%A0.assets/image-20211201182227746-16383541515593.png)]

    WPA3 是 WFA 新制定的适用于个人网络和企业网络的安全标准。WPA3 旨在通过使用现代安全算法和更强大的加密套件来提高 WLAN 的整体安全性。WPA3 分为两个部分:

    • WPA3-Personal:使用对等实体同时验证 (SAE) 而不是预共享密钥 (PSK),可以为用户提供更强大的安全保护,抵御离线字典攻击、密钥恢复和消息伪造等攻击。
    • WPA3-Enterprise:提供更强大的身份验证和链路层加密方法,以及适用于敏感安全环境的可选 192 位安全模式。

    Wi-Fi Enhanced Open 是 WFA 新推出的适用于公共网络的安全标准,基于机会性无线加密 (OWE)。Wi-Fi Enhanced Open 可以为咖啡馆、酒店、餐馆和图书馆等场所内无密码保护的开放网络提供加密和隐私保护。Enhanced Open 不提供身份验证。

    WPA3 和 Wi-Fi Enhanced Open 可以提升 WLAN 的整体安全性,从而提供更好的隐私保护和稳健性来抵御已知攻击。由于许多设备尚不支持这些标准或尚未进行软件升级来支持这些功能,因此 WFA 提供了以下过渡模式:

    • WPA2/WPA3 过渡模式:服务接入点同时支持 WPA2 和 WPA3 标准。在该模式下,搭载 Android 10 的设备使用 WPA3 建立连接,搭载 Android 9 或更低版本的设备使用 WPA2 连接到同一接入点。
    • WPA2/WPA3-Enterprise 过渡模式:服务接入点同时支持 WPA2-Enterprise 和 WPA3-Enterprise 标准。
    • OWE 过渡模式:服务接入点同时支持 OWE 和开放式标准。在该模式下,搭载 Android 10 的设备使用 OWE 建立连接,搭载 Android 9 或更低版本的设备无需任何加密即可连接到同一接入点。

    Android 12 支持“过渡停用”指示,这是一种指示设备不使用 WPA2 而改用 WPA3 的机制。设备收到此指示后,会使用 WPA3 连接到支持过渡模式的 WPA3 网络。Android 12 还支持 WPA3 Hash-to-Element (H2E) 身份验证交换。

    WPA3 和 Wi-Fi Enhanced Open 仅在客户端模式下受支持。

    Wifi Framework

    Wifi工作步骤:

    • WiFi初始化
    • WiFi启动
    • 扫描
    • 显示扫描的AP
    • 配置AP
    • 连接AP
    • 获取IP地址
    • 上网
    • WPA2/WPA3 过渡模式:服务接入点同时支持 WPA2 和 WPA3 标准。在该模式下,搭载 Android 10 的设备使用 WPA3 建立连接,搭载 Android 9 或更低版本的设备使用 WPA2 连接到同一接入点。
    • WPA2/WPA3-Enterprise 过渡模式:服务接入点同时支持 WPA2-Enterprise 和 WPA3-Enterprise 标准。
    • OWE 过渡模式:服务接入点同时支持 OWE 和开放式标准。在该模式下,搭载 Android 10 的设备使用 OWE 建立连接,搭载 Android 9 或更低版本的设备无需任何加密即可连接到同一接入点。

    Android 12 支持“过渡停用”指示,这是一种指示设备不使用 WPA2 而改用 WPA3 的机制。设备收到此指示后,会使用 WPA3 连接到支持过渡模式的 WPA3 网络。Android 12 还支持 WPA3 Hash-to-Element (H2E) 身份验证交换。

    WPA3 和 Wi-Fi Enhanced Open 仅在客户端模式下受支持。

    Wifi Framework

    Wifi工作步骤:

    • WiFi初始化
    • WiFi启动
    • 扫描
    • 显示扫描的AP
    • 配置AP
    • 连接AP
    • 获取IP地址
    • 上网
    更多相关内容
  • Android-WIFI(扫描 连接

    多人点赞 热门讨论 2021-05-29 10:45:26
    公司想着把自己的产品(中央报警器)设置为主页面让用户永远只能在这款产品上面进行一个操作,当时我写的时候连接WIFI是需要进入系统设置页面去连接的(主要是当时我也不会写WIFI功能啊!!!)然后不想什么就来什么...

    公司想着把自己的产品(中央报警器)设置为主页面让用户永远只能在这款产品上面进行一个操作,当时我写的时候连接WIFI是需要进入系统设置页面去连接的(主要是当时我也不会写WIFI功能啊!!!)然后不想什么就来什么了!产品让我把WIFI功能写一下(嘤嘤嘤,我才是刚入Android的小白,我怎么写啊!脑阔疼脑阔疼)

    需求

    获取wifi列表 显示wifi强度 连接wifi 显示wifi连接信息(只是我理解出来的,当时产品说让我写个wifi脱离系统QAQ,wifi脱离系统是怎么个连接wifi法?问他也是说:这是你的专业范围,你自己去理解。微笑面对一切)

    然后发了一顿牢骚:没做过呀,我不会啊,这怎么写啊,闲聊两小时不如百度一分钟(其实是差不多把所有wifi的案例都看完了)

    网上有很多关于获取wifi列表,并连接wifi的文章,然后我每一篇都点来看看。然后发现,耶,还蛮有趣的!一如研究深似海,发现了各种问题,然后自己记在小本本上,万一我遇见了呢?是不是就不用死的太惨了~

    权限

    虽然是刚入Android的小白,但是发现安卓的每一次更新都是权限的加强,刚开始的时候就是给权限折磨的要死!!!记得我写电话短信功能的时候,给了权限但是就是拨打不了手机号也发送不了短信,后面还是给了权限在手动打开,下面献上关于wifi的权限好叭!!!

    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" ></uses-permission>
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" ></uses-permission>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" ></uses-permission>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" ></uses-permission>
    

    你们以为这就完了???哇,太天真了叭。确实,很老很老的版本确实可以这样就完事了,但是6.0版本后的就需要定位了叭(这个是我网上看到的,不是我也不知道,反正我知道现在是需要就对了嘻嘻)继续给大家献上后面的权限

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    

    好了,你权限都加完了。但是发现还是不行,你就跑过来问我,小李怎么回事啊?不行啊,我都加了权限了为什么还是获取不到wifi的列表呢?(呆子!!!我都说了要手动打开手动打开了!不知道怎么手动打开嘛?那我继续把代码给你好了呀!满意了叭)

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(
                    Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ) {
                requestPermissions(new String[] { Manifest.permission.CALL_PHONE,Manifest.permission.ACCESS_COARSE_LOCATION},
                        0x1234);
            }
    

    你们也可以用这种方式去手动打开其他的权限,百度上一大堆(百度yyds!!!)
    权限的问题解决了那就是编码了!但你打开权限你就成功了一半了!反正我是这么认为的,编码嘛easy啦!

    编码部分

    掐指一算,有些人是不是会问,既然权限给了,我怎么编码扫描wifi呢?
    唉,那你是真的跟三天前的我一样,啥也不会啊我的宝,给你吧给你吧,谁要小李心好呢?
    扫描wifi

    public void startScan() {
    		wifiManager.startScan();
    		// 扫描返回结果列表
    		scanResultList = wifiManager.getScanResults();
    		// 扫描配置列表
    		wifiConfigList = wifiManager.getConfiguredNetworks();
    	}
    

    噗嗤,你不会还要我教你怎么去显示叭?哎呀,这个也就几行代码的事情了呀

    	@SuppressLint("NewApi")
    	private ArrayList<WifiElement> getAllNetWorkList() {
            // 每次点击扫描之前清空上一次的扫描结果
            mWifiElementList.clear();
            if(mWifiAdmin.isWifiEanbled()){
                // 开始扫描网络
                mWifiAdmin.startScan();
            }else{
                Toast.makeText(getApplicationContext(), "请先打开wifi!", Toast.LENGTH_SHORT).show();
            }
            mScanResultList = mWifiAdmin.getWifiList();
            wifiConfigList = mWifiAdmin.getWifiConfigList();
            WifiElement element;
            if (mScanResultList != null) {
                for (int i = 0; i < mScanResultList.size(); i++) {
                    // 得到扫描结果
                    mScanResult = mScanResultList.get(i);
                    if (!mScanResult.SSID.equals("") || mScanResult.SSID == ""){
                        element = new WifiElement();
                        element.setSsid(mScanResult.SSID);
                        element.setBssid(mScanResult.BSSID);
                        element.setStatus(cheackStatus(mScanResult.SSID));
                        element.setFrequency(mScanResult.frequency);
                        element.setLevel(mScanResult.level);
                        mWifiElementList.add(element);
                    }
                }
            }
            return mWifiElementList;
        }
    

    还有显示内容的容器啊这些就不用我在多写了叭?(要源码的可以私聊我,我直接给你!小李有点懒,不想都写了!!!)

    wifi开关变化

     @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            switch (v.getId()) {
                case R.id.wifi_conn_switch_btn:
                    mWifiConnAdapter = new WifiConnListAdapter(getApplicationContext(), getAllNetWorkList());
                    mLvWifiConInfo.setAdapter(mWifiConnAdapter);
                    break;
                case R.id.wifi_conn_scan_btn:
                    if (isOpen) {
                        Toast.makeText(getApplicationContext(), "正在关闭wifi", Toast.LENGTH_SHORT).show();
                        if (mWifiAdmin.closeWifi()) {
                            Toast.makeText(getApplicationContext(), "wifi关闭成功", Toast.LENGTH_SHORT).show();
                            mBtnWifiScan.setText("打开wifi");
                            isOpen = false;
                        } else {
                            Toast.makeText(getApplicationContext(), "wifi关闭失败", Toast.LENGTH_SHORT).show();
                        }
                    } else {
                        Toast.makeText(getApplicationContext(), "正在打开wifi", Toast.LENGTH_SHORT).show();
                        if (mWifiAdmin.OpenWifi()) {
                            Toast.makeText(getApplicationContext(), "wifi打开成功", Toast.LENGTH_SHORT).show();
                            mBtnWifiScan.setText("关闭wifi");
                            isOpen = true;
                        } else {
                            Toast.makeText(getApplicationContext(), "wifi打开失败", Toast.LENGTH_SHORT).show();
                        }
                    }
                    break;
                default:
                    break;
            }
        }
    

    打开wifi

    public boolean OpenWifi() {
    		boolean bRet = true;
    		if (!wifiManager.isWifiEnabled()) {
    			bRet = wifiManager.setWifiEnabled(true);
    		}
    		return bRet;
    	}
    

    关闭wifi

    public boolean closeWifi() {
    		if (!wifiManager.isWifiEnabled()) {
    			return true;
    		} else {
    			return wifiManager.setWifiEnabled(false);
    		}
    	}
    

    连接wifi
    这里的话肯定是要去传入它必要的几个参数啊
    就比如ssid pwd type

    private WifiConfiguration CreateWifiInfo(String SSID, String Password,
    											 WifiCipherType Type) {
    		WifiConfiguration wc = new WifiConfiguration();
    		wc.allowedAuthAlgorithms.clear();
    		wc.allowedGroupCiphers.clear();
    		wc.allowedKeyManagement.clear();
    		wc.allowedPairwiseCiphers.clear();
    		wc.allowedProtocols.clear();
    		wc.SSID = "\"" + SSID + "\"";
    		if (Type == WifiCipherType.WIFICIPHER_NOPASS) {
    			wc.wepKeys[0] = "";
    			wc.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
    			wc.wepTxKeyIndex = 0;
    		} else if (Type == WifiCipherType.WIFICIPHER_WEP) {
    			wc.wepKeys[0] = "\"" + Password + "\"";
    			wc.hiddenSSID = true;
    			wc.allowedAuthAlgorithms
    					.set(WifiConfiguration.AuthAlgorithm.SHARED);
    			wc.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
    			wc.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
    			wc.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
    			wc.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
    			wc.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
    			wc.wepTxKeyIndex = 0;
    			// System.out.println(wc.preSharedKey);
    			System.out.println(wc);
    		} else if (Type == WifiCipherType.WIFICIPHER_WPA) {
    			wc.preSharedKey = "\"" + Password + "\"";
    			wc.hiddenSSID = true;
    			// 用来判断加密方法。
    			// 可选参数:LEAP只用于leap,
    			// OPEN 被wpa/wpa2需要,
    			// SHARED需要一个静态的wep key
    			wc.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
    			// 用来判断加密方法。可选参数:CCMP,TKIP,WEP104,WEP40
    			wc.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
    			wc.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
    			// WifiConfiguration.KeyMgmt 键管理机制(keymanagerment),使用KeyMgmt 进行。
    			// 可选参数IEEE8021X,NONE,WPA_EAP,WPA_PSK
    			wc.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
    			// WifiConfiguration.PairwiseCipher 设置加密方式。
    			// 可选参数 CCMP,NONE,TKIP
    			wc.allowedPairwiseCiphers
    					.set(WifiConfiguration.PairwiseCipher.TKIP);
    			wc.allowedPairwiseCiphers
    					.set(WifiConfiguration.PairwiseCipher.CCMP);
    			// WifiConfiguration.Protocol 设置一种协议进行加密。
    			// 可选参数 RSN,WPA,
    			wc.allowedProtocols.set(WifiConfiguration.Protocol.WPA); // for WPA
    			wc.allowedProtocols.set(WifiConfiguration.Protocol.RSN); // for WPA2
    			// wifiConfiguration.Status 获取当前网络的状态。
    		} else {
    			return null;
    		}
    		return wc;
    	}
    

    咳咳,写到这里也该告一段落了,欢迎各位大佬指点!!!不足之处请无情~

    展开全文
  • app里有个需求就是在应用内部开发一个wifi设置的功能,避免用户跳到手机wifi设置界面操作,之前没开发过这样的需求,只是简单的判断网络状态,不过心想应该不难,都是挺成熟的东西,其实做了后才知道还是有些坑的,...

    前言

    app里有个需求就是在应用内部开发一个wifi设置的功能,避免用户跳到手机wifi设置界面操作,之前没开发过这样的需求,只是简单的判断网络状态,不过心想应该不难,都是挺成熟的东西,其实做了后才知道还是有些坑的,同时也看了网络上的一些文章,只是大都是讲一些小的方面或者讲的很简陋,于是就自己重新整理了下wifi完整操作的内容;背景是app的wifi设置需求

    其demo效果如图
    在这里插入图片描述

    需求

    1. 手机没有打开wifi,需要自动打开wifi
    2. 获取可用的wifi列表,如果某个wifi是连接状态,那就用(已连接)标注
    3. 时刻刷新wifi列表,也就是当有可用的wifi热点出现时,要在列表上体现,wifi列表按信号强度倒序排列
    4. 输入密码连接wifi,如果之前已经连接了某个wifi,那就断开那个连接,但是不能清除它的信息,否则下次连接那个wifi时还得重新输入密码
    5. 时刻监听wifi按钮的打开、关闭状态
    6. 获取wiif的mac地址和ip地址

    实现

    既然需求已经明确了,那就动手吧,这里面涉及到的api主要有

    • WifiManager:此类提供了用于管理Wi-Fi连接所有方面的主要API,比如查看wifi状态,打开关闭wifi按钮,获取wifi的ip地址,名称,路由器wifi的mac地址及本机无线网卡的mac地址,扫描wifi列表,已配置的网络列表,连接wifi等
    • WifiInfo:封装了一个已连接的wifi对象,可以获取当前这个连接的wifi的一些信息,比如wifi名,网络id,状态
    • WifiConfiguration:看名字也知道是保存了一些wifi配置的信息,比如WiFi名,wifi密码,代理,加密算法,密钥等,通常用于在连接wifi的时候,需要配置这个wifi的信息,这个信息就是用这个类来表示的,不过这个类在api29中标识为废弃,将来它将成为仅系统使用的对象,使用使用WifiNetworkSpecifier.Builder创建NetworkSpecifier和WifiNetworkSuggestion.Builder创建WifiNetworkSuggestion来代替
    • ScanResult:当扫描周围wifi热点的时候,会得到一个列表,其泛型就是它,也就是说一个ScanResult是封装了一个wifi热点信息的对象,包括WiFi名,信号强度,路由器mac地址,加密方案等

    使用前需要申请相关权限

        <uses-permission android:name="android.permission.INTERNET"></uses-permission>
        <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> <!-- 允许程序改变网络链接状态 -->
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <!-- 允许程序访问网络链接状态 -->
        <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <!-- 允许程序访问WIFI网络状态信息 -->
        <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <!-- 允许程序改变WIFI链接状态 -->
    

    光有这个网络权限还不够,还需要申请定位权限,这点其实挺烦的,给用户带来困惑,你就是设置个wifi,为啥还要申请定位权限,是不是想干啥xxx;感觉没有ios做的清晰,wifi就是wifi,定位就是定位

        <!--使用wifi及蓝牙功能 需要开启定位才能搜索到蓝牙设备或者wifi-->
        <!--如果app targets 在android9.0(api 28)或者更低,只需要定义ACCESS_COARSE_LOCATION权限-->
        <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    

    为了方便调用,新建一个wifi功能的统一管理类,如下

    public class WifiManager {
    
        private android.net.wifi.WifiManager mWifiManager;
        private Context                      mContext;
    
        private static class Holder{
            private static final WifiManager INSTANCE = new WifiManager();
        }
        private WifiManager(){
            mContext = Configurator.getConfig().getApplicationContext();
        }
        public static WifiManager getDefault(){
            return Holder.INSTANCE;
        }
    
        public void openWifiManager(){
            mWifiManager = (android.net.wifi.WifiManager)mContext .getSystemService(Context.WIFI_SERVICE);
        }
     }
    

    接下来就将一些通用的功能写在这个类里,比如

    检测wifi是否打开

        public boolean checkWifiOpen(){
            return mWifiManager.isWifiEnabled();
        }
    

    打开和关闭wifi

        // 打开WIFI
        public void openWifi() {
            if (!mWifiManager.isWifiEnabled()) {
                mWifiManager.setWifiEnabled(true);
            }
        }
    
        // 关闭WIFI
        public void closeWifi() {
            if (mWifiManager.isWifiEnabled()) {
                mWifiManager.setWifiEnabled(false);
            }
        }
    

    有同学可能想了,方法调用了,那怎么知道打开或者关闭成功了呢?很简单,通过接受wifi相应的广播就可以知道了,放在后面讲

    获取wifi状态

        public String getWifiState(){
            switch (mWifiManager.getWifiState()) {
                case android.net.wifi.WifiManager.WIFI_STATE_DISABLED:
                    return "WI-FI未开启";
                case android.net.wifi.WifiManager.WIFI_STATE_DISABLING:
                    return "WI-FI正关闭中";
                case android.net.wifi.WifiManager.WIFI_STATE_ENABLED:
                    return "WI-FI已开启";
                case android.net.wifi.WifiManager.WIFI_STATE_ENABLING:
                    return "WI-FI正在开启中";
                case android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN:
                    return "WI-FI功能未知";
            }
            return "WIFI功能未知";
        }
    

    获取连接wifi的名称,通过WifiInfo 对象获取,需要注意的是返回的名称里带有“,所以需要替换掉,这在后面登录wifi时也需要添加上

        //获取连接wifi名称 TP-LINK_7BDE4C
        public String getWifiSSID(){
            final android.net.wifi.WifiManager manager = mWifiManager;
            if(manager.isWifiEnabled()){
                WifiInfo wifiInfo = manager.getConnectionInfo();
                if (wifiInfo == null) {
                    return null;
                }
                String ssid = wifiInfo.getSSID();
                if(!TextUtils.isEmpty(ssid)){
                    if(ssid.contains("\"")){
                        return ssid.replaceAll("\"","");
                    }else{
                        return ssid;
                    }
                }
            }
            return null;
        }
    

    获取ip地址

        //获取wifi的ip地址 这是分配给终端的 ,每个终端不同 比如10.47.105.33
        public String getWifiAddress(){
            final android.net.wifi.WifiManager manager = mWifiManager;
            if(manager.isWifiEnabled()){
                WifiInfo wifiInfo = manager.getConnectionInfo();
                if (wifiInfo == null) {
                    return null;
                }
                String address = formatIpAddress(wifiInfo.getIpAddress());
                return address;
            }
            return null;
        }
    

    获取路由器的mac地址

        //获取当前连接的wifi路由器的MAC地址 ,每个终端不同 00:1a:95:94:ea:fc
        public String getRouteMacAddress(){
            final android.net.wifi.WifiManager manager = mWifiManager;
            if(manager.isWifiEnabled()){
                WifiInfo wifiInfo = manager.getConnectionInfo();
                if (wifiInfo == null) {
                    return null;
                }
                String macAddress;
                macAddress = wifiInfo.getBSSID();
                return macAddress;
            }
            return null;
        }
    

    获取手机无线网卡的mac地址,这里需要注意版本的区别,在6.0之前是可以通过WifiInfo 对象获取,但是6.0开始,google意识到这侵犯了用户隐私,于是屏蔽了该api,开发者只能获取到02:00:00:00:00:00这样的字符串,需要通过一些特殊的方法获取,比如代码里这样

        /**
         * 获取本机无线网卡的MAC地址
         * @return
         */
        public String getLocalMacAddress(){
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                return getMacAddr();
            } else {
                final android.net.wifi.WifiManager manager = mWifiManager;
                if(manager.isWifiEnabled()){
                    WifiInfo wifiInfo = manager.getConnectionInfo();
                    if (wifiInfo == null) {
                        return null;
                    }
                    String macAddress;
                    macAddress = wifiInfo.getMacAddress();
                    return macAddress;
                }
            }
            return null;
        }
    
    
        public String getMacAddr() {
            try {
                List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
                for (NetworkInterface nif : all) {
                    if (!nif.getName().equalsIgnoreCase("wlan0")) continue;
                    byte[] macBytes = nif.getHardwareAddress();
                    if (macBytes == null) {
                        return "";
                    }
                    StringBuilder res1 = new StringBuilder();
                    for (byte b : macBytes) {
                        res1.append(String.format("%02X:",b));
                    }
                    if (res1.length() > 0) {
                        res1.deleteCharAt(res1.length() - 1);
                    }
                    return res1.toString();
                }
            } catch (Exception ex) {
            }
            return "02:00:00:00:00:00";
        }
    

    扫描wifi列表

    接下来比较重要的一步就是获取周围的wifi热点,既然在手机里做wifi设置功能,总得获取到周围的wifi列表吧,通过如下方法即可实现

        public List<ScanResult> getScanResults(){
            final android.net.wifi.WifiManager wifiManager = mWifiManager;
            List<ScanResult> scanResults = wifiManager.getScanResults();
            return scanResults;
        }
    

    直接使用ScanResult这个系统api不太方便,我们可以自定义一个wifi对象,然后将其进行转换,比如

    public class WifiBean extends SheetItem implements Comparable<WifiBean>{
    
        private String wifiName;
        private String signalLevel;
        private String routeMac;
        private String capabilities;
    
        private String pwd;
        private String state;
    }
    
    • wifiName就是WiFi名,对应着ScanResult的SSID,这里需要注意有很多ScanResult的SSID是空的
    • signalLevel是信号强度,对应着ScanResult的level,但这个值是负数,可以通过WifiManager.calculateSignalLevel(scanResults.get(0).level,10)计算它的强度,区间[0,10)
    • routeMac就是路由器WIFI的MAC地址,对应ScanResult的BSSID
    • capabilities就是加密方案,对应着ScanResult的capabilities,包括接入点支持的认证、密钥管理、加密机制等

    其它两个字段是自己维护的,pwd就是wifi登录密码,用户输入的;state就是某个wifi热点的状态,有已连接 正在连接 未连接 三种状态;判断wifi列表中的某个wifi是不是当前连接的wifi,通过比对mac地址就行了

    wifi广播

    还有一个动态刷新的问题,因为上面的方法获取到的wifi列表是一次性的,后面新增加的wifi或者消失的wifi是没办法在这个wifi列表体现的,这时候就需要广播了

        @Override
        public void registerReceiver() {
            connectReceiver = new NetConnectReceiver();
            IntentFilter filter = new IntentFilter();
            //监听网络连接状态广播
            filter.addAction(android.net.wifi.WifiManager.NETWORK_STATE_CHANGED_ACTION);
            //监听wifi开关变化的状态
            filter.addAction(android.net.wifi.WifiManager.WIFI_STATE_CHANGED_ACTION);
            //监听wifi列表变化,比如减少或新增一个wifi热点
            filter.addAction(android.net.wifi.WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
            //监听wifi连接的过程, 包含可能会出现连接错误的错误码
            filter.addAction(android.net.wifi.WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
            Configurator.getConfig().getApplicationContext().registerReceiver(connectReceiver,filter);
        }
    

    WifiManager.SCAN_RESULTS_AVAILABLE_ACTION这个广播可以知道当前设备wifi列表的变化,其它几个广播是跟wifi连接有关的,后面连接的时候在讲

    接下来在onReceive方法中判断下就行了

    if (android.net.wifi.WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
                // 这个监听wifi列表的变化,比如新搜索到了或者减少了一个wifi热点
                mEvent.setTag(WIFI_HOTSPOT_ADD);
                EventBus.getDefault().post(mEvent);
    } 
    

    wifi连接登录

    接下来就是最重要的一步了,登录wifi,但在登录前需要做一些判断,比如

    • 当前手机正在尝试连接另一个wifi,但是因为某些原因,比如密码错误造成身份验证失败,连接断开,这时候就需要移除掉这个wifi对应的网络配置
    • 当前手机正与另一个wifi连接上了,那需要禁用掉这个wifi,这里切记不要像第一步那样直接移除掉它的网络配置,因为下次要是重新连接这个wifi的话,就需要再输一次密码了,那用户肯定对你这个app心里是有一万个草泥马的
    • 如果手机已经与用户选的这个wifi连接上了,那就不需要再重复登录了,直接返回
    
        public boolean checkWifiConnect(String ssid){
            WifiInfo connectionInfo = mWifiManager.getConnectionInfo();
            if (connectionInfo != null) {
    
                SupplicantState supplicantState = connectionInfo.getSupplicantState();
                if (SupplicantState.DISCONNECTED == supplicantState) {
                    if (connectionInfo.getNetworkId() != -1) {
                        mWifiManager.removeNetwork(connectionInfo.getNetworkId());
                    }
                    return false;
                }
    
                String connectSsid = connectionInfo.getSSID();
                //如果当前已经连接了网络,但需要连接到其它wifi,那就断开当前wifi
                if (!TextUtils.isEmpty(connectSsid) && !TextUtils.equals(ssid,connectSsid)) {
                    if (connectionInfo.getNetworkId() != -1) {
                        mWifiManager.disableNetwork(connectionInfo.getNetworkId());
                    }
                    return false;
                }
    
                if (TextUtils.equals(ssid,connectSsid)) {
                    return true;
                }
            }
            return false;
        }
    

    最后就是登录wifi了,这里主要是操作WifiConfiguration这个对象,这里也分几步:

    • 第一步就是格式化wifi名和密码,需要在用户输入的WiFi名和密码字符串前后都加上"字符
    • 第二步就是看看这个wifi之前是不是已经登录过了,如果登录过了就没必要重复配置了,这是通过在设备保存的WifiConfiguration列表中找一找有没有跟用户选的这个wifi名一样的,如果有就直接启用这个wifi就行了
    • 第三步就需要重新生成一个WifiConfiguration了,然后配置它的wifi名,密码,加密方式,至于为什么需要这样配置,大家可以参考android系统源码,因为手机系统有一个自带的系统设置app,里面有设置wifi的模块,所以大家如果做的功能,系统也自带了,那可以参考系统是如何实现的,我这里是android7.0下面的,所以源码路径在
      android-7.0.0_r1\packages\apps\Settings\src\com\android\settings\wifi\WifiConfigController.java
        public int connectWifi(String targetSsid,String targetPsd,String cipher){
    
            // 1、注意热点和密码均包含引号
            String ssid = '"' + targetSsid + '"';
            String psd = '"' + targetPsd + '"';
    
            if (checkWifiConnect(ssid)) {
                return WIFI_LOGIN_RESULT_REPEAT;
            }
    
            //2、配置wifi信息
            WifiConfiguration wifiConfig = findConfigWork(ssid);
            if (wifiConfig != null) {
                L.i(TAG,"connectWifi wifiConfig="+wifiConfig.SSID);
                mCurnentNetId = wifiConfig.networkId;
                mWifiManager.enableNetwork(mCurnentNetId,true);
            } else {
                L.i(TAG,"connectWifi ssid="+ssid+",psd="+psd);
                wifiConfig = new WifiConfiguration();
    
                wifiConfig.SSID = ssid;
                switch (getCipherType(cipher)) {
                    case WIFI_ENCRYPTION_WPA:
                        wifiConfig.preSharedKey = psd;
                        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
                        break;
                    case WIFI_ENCRYPTION_WEP:
                       wifiConfig.wepKeys[0] = psd;
                        wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
                        wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
                        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
                        break;
                    case WIFI_ENCRYPTION_OPEN:
                        //无需密码 直连
                        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
                        break;
                    default:
                }
                mCurnentNetId = mWifiManager.addNetwork(wifiConfig);
                mWifiManager.enableNetwork(mCurnentNetId,true);
            }
            return mCurnentNetId;
        }
    

    至于登录结果也是通过广播来获取的,下面这个广播监听wifi的连接状态,即是否连上了一个有效无线路由

    if (android.net.wifi.WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
              
                Parcelable parcelable = intent.getParcelableExtra(android.net.wifi.WifiManager.EXTRA_NETWORK_INFO);
                if (parcelable == null) {
                    return;
                }
                NetworkInfo info = (NetworkInfo) parcelable;
                NetworkInfo.State state = info.getState();
                switch (state) {
                    case CONNECTED:
                        // 已连接上
                        MessageEvent event = new MessageEvent();
                        event.setTag(WIFI_STATE_CONNECTED);
                        EventBus.getDefault().post(event);
                        break;
                    case CONNECTING:
                        //正在连接
                        MessageEvent event2 = new MessageEvent();
                        event2.setTag(WIFI_STATE_ON_CONNECTING);
                        EventBus.getDefault().post(event2);
                        break;
                    case DISCONNECTED:
                        //已断开
                        WifiManager.getDefault().removeNetWork();
                        MessageEvent event3 = new MessageEvent();
                        event3.setTag(WIFI_STATE_DISCONNECT);
                        EventBus.getDefault().post(event3);
                        break;
                    case DISCONNECTING:
                        //断开中
                        break;
                        default:
                }
    
            }
    

    这个监听wifi的打开与关闭,与wifi的连接无关

    if (android.net.wifi.WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
                int state = intent.getIntExtra(android.net.wifi.WifiManager.EXTRA_WIFI_STATE, 0);
                switch (state) {
                    case android.net.wifi.WifiManager.WIFI_STATE_DISABLED:
                        //wifi已关闭
                        MessageEvent event = new MessageEvent();
                        event.setTag(WIFI_STATE_UNOPEN);
                        EventBus.getDefault().post(event);
                        break;
                    case android.net.wifi.WifiManager.WIFI_STATE_DISABLING:
                        //wifi关闭中
                        /*MessageEvent event2 = new MessageEvent();
                        event2.setTag(WIFI_STATE_UNOPEN);
                        EventBus.getDefault().post(event2);*/
                        break;
                    case android.net.wifi.WifiManager.WIFI_STATE_ENABLED:
                        //wifi已打开
                        MessageEvent event3 = new MessageEvent();
                        event3.setTag(WIFI_STATE_OPENED);
                        EventBus.getDefault().post(event3);
                        break;
                    case android.net.wifi.WifiManager.WIFI_STATE_ENABLING:
                        //wifi打开中
                        MessageEvent event4 = new MessageEvent();
                        event4.setTag(WIFI_STATE_ON_OPENING);
                        EventBus.getDefault().post(event4);
                        break;
                    default:
                }
            } 
    

    总结

    到这里基本上wifi该有的功能已经够app使用了,当然还有一些小的方面,比如设置代理等,因为目前我的app还没有这个需求,就没有在这里添加了,等后面看看,如果需要就加上吧

    展开全文
  • Android WifiDirect

    2020-08-28 14:12:06
    WiFi直连也就是WiFi设备点对点连接WiFi P2P),它允许具有适当硬件的Android 4.0(API级别14)或更高版本的设备通过Wi-Fi直接相互连接,而无需中间接入点。使用这些API,您可以发现并连接到其他设备(前提是每个...

    Android WifiDirect

    1. 简介

    WiFi直连也就是WiFi设备点对点连接(WiFi P2P),它允许具有适当硬件的Android 4.0(API级别14)或更高版本的设备通过Wi-Fi直接相互连接,而无需中间接入点。使用这些API,您可以发现并连接到其他设备(前提是每个设备支持Wi-Fi P2P),然后通过比蓝牙连接更长的距离快速连接进行通信。这对于在用户之间共享数据的应用程序很有用,例如多人游戏或照片共享应用程序。

    Wi-Fi P2P API包含以下主要部分
    允许您发现,请求和连接到对等的方法在WifiP2pManager类中定义。
    允许您通知WifiP2pManager方法调用成功或失败的监听器。调用WifiP2pManager方法时,每个方法都可以接收作为参数传入的特定侦听器。
    通知您Wi-Fi P2P框架检测到的特定事件的意图,例如断开的连接或新发现的对等体。
    您经常将API的这三个主要组件一起使用。例如,您可以提供WifiP2pManager.ActionListener呼叫discoverPeers(),以便您可以使用ActionListener.onSuccess()和ActionListener.onFailure() 方法通知您。

    2. 使用流程

    1.权限获取

    在manifest文件当中声明相关权限,同时声明你应用的SDK 最小版本号,因为Android4.0才开始支持WiFi直连,所以版本号是14。

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
    <uses-permission android:name="android.permission.INTERNET" />
    
    2.编写广播接收器

    接收Wifi状态变化的广播

    public class WiFiDirectBroadcastReceiver extends BroadcastReceiver {
    
    
            @Override
            public void onReceive(Context context, final Intent intent) {
                String action = intent.getAction();
                Log.d("wny", "onReceive: " + action.toString());
    
                if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
                    NetworkInfo networkInfo = (NetworkInfo) intent
                            .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
                    if (networkInfo.isConnected()) {
                        // we are connected with the other device, request connection
                        // info to find group owner IP
                        mManager.requestConnectionInfo(mChannel, WifiP2PActivity.this);
                    } else {
                        //断开连接置空IP地址
                        mDeviceIp = null;
                        // It's a disconnect
                    }
                }
    
    
                if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
                    //检测 WIFI 功能是否被打开
                    int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,-1);
                    if (state == 1) {
                        Toast.makeText(WifiP2PActivity.this, "请打开Wlan", 																	Toast.LENGTH_SHORT).show();
                    }
                } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) 			{
                    boolean flag = false;
                    //获取当前可用连接点的列表
                    WifiP2pDeviceList wifiP2pDeviceList = 											intent.getParcelableExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST);
                    Log.d(TAG, "onReceive: intent" + intent.toString());
                    List<WifiP2pDevice> p2pDeviceList = new ArrayList<>();
                    p2pDeviceList.addAll(wifiP2pDeviceList.getDeviceList());
                    for (WifiP2pDevice wifiP2pDevice : p2pDeviceList) {
                        Log.d(TAG, "onReceive: "+wifiP2pDevice.deviceName);
                        if (wifiP2pDevice.deviceName.equals("电子相框")) {
                            mDeviceAdress = wifiP2pDevice.deviceAddress;
                            //Toast.makeText(WifiP2PActivity.this, "检索到设备", Toast.LENGTH_SHORT).show();
                            flag = true;
                            break;
                        }
                    }
                    if (!flag){
                        Toast.makeText(WifiP2PActivity.this,"没有检索到可用设																备",Toast.LENGTH_SHORT).show();
                    }
                    if (p2pDeviceList.size() !=0) {
                        Log.d("wny", "" + p2pDeviceList.get(0).toString());
    
                    }
    
    
                } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
    
                    //建立或者断开连接
                } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
                    //当前设备的 WIFI 状态发生变化
                }
            }
        }
    

    Channel(信道)类的作用:

    ​ 将应用程序连接到Wifi p2p框架的通道,大多数p2p操作都需要一个通道作为参数。得到信道实例。

    public static class Channel implements AutoCloseable {
            /** @hide */
            public Channel(Context context, Looper looper, ChannelListener l, Binder binder,
                    WifiP2pManager p2pManager) {
                mAsyncChannel = new AsyncChannel();
                mHandler = new P2pHandler(looper);
                mChannelListener = l;
                mContext = context;
                mBinder = binder;
                mP2pManager = p2pManager;
    
                mCloseGuard.open("close");
            }
        
        
        ........
    }
    
    3.初始化广播过滤器和广播接收器
    mIntentFilter = new IntentFilter();
           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);
    mManager = (WifiP2pManager) this.getSystemService(Context.WIFI_P2P_SERVICE);
    mChannel = mManager.initialize(this, this.getMainLooper(), null);//生成Channel实例
    mReceiver = new WiFiDirectBroadcastReceiver();
    this.registerReceiver(mReceiver, mIntentFilter);//注册广播接收者
    

    在你的Activity的onCreate方法当中,获取WifiP2pManager的实例。然后调用initialize()方法来将你的应用注册到 Wi-Fi P2P framework当中,这个方法将会返回一个WifiP2pManager.Channel对象,它把你的应用与底层的 Wi-Fi P2P framework连接起来

    **Manager.initialize **():

    使用Wi-Fi框架注册应用程序。这个函数在执行任何p2p操作之前必须首先调用。

    @param srcContext是源的上下文

    @param srcLooper是接收回调的循环程序

    @param侦听器,用于在失去框架通信时回调。可以为空。

    @return Channel实例,这是执行任何进一步p2p操作所必需的

     /**
         * Registers the application with the Wi-Fi framework. This function
         * must be the first to be called before any p2p operations are performed.
         *
         * @param srcContext is the context of the source
         * @param srcLooper is the Looper on which the callbacks are receivied
         * @param listener for callback at loss of framework communication. Can be null.
         * @return Channel instance that is necessary for performing any further p2p operations
         */
        public Channel initialize(Context srcContext, Looper srcLooper, ChannelListener listener) {
            Binder binder = new Binder();
            Channel channel = initalizeChannel(srcContext, srcLooper, listener, getMessenger(binder),
                    binder);//getMessenger(binder)获取对WifiP2pService处理程序的引用。这是用来建立与WifiService的异步信道通信(IWifiP2pManager.getMessenger(binder))
            return channel;
        }
    

    getMessenger方法如下:

    //获取对WifiP2pService处理程序的引用。这是用来建立
    //与WifiService的异步信道通信
    //@param binder服务与此客户端关联的绑定器。
    //@return Messenger指向WifiP2pService处理程序
        public Messenger getMessenger(Binder binder) {
            try {
                return mService.getMessenger(binder);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    

    下边的方法,在调用getSystemService(Context.WIFI_P2P_SERVICE)后会执行,传入IWifiP2pManager实现类赋值给mService,用于后边的调用。

    /**
         * Create a new WifiP2pManager instance. Applications use
         * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
         * the standard {@link android.content.Context#WIFI_P2P_SERVICE Context.WIFI_P2P_SERVICE}.
         * @param service the Binder interface
         * @hide - hide this because it takes in a parameter of type IWifiP2pManager, which
         * is a system private class.
         */
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
        public WifiP2pManager(IWifiP2pManager service) {
            mService = service;
        }
    
    /**
     * Interface that WifiP2pService implements
     *
     * {@hide}
     */
    public interface IWifiP2pManager extends android.os.IInterface
    

    WifiP2pService如下:

    public final class WifiP2pService extends SystemService {
    
        private static final String TAG = "WifiP2pService";
        final WifiP2pServiceImpl mImpl;//实现类
    
        public WifiP2pService(Context context) {
            super(context);
            mImpl = new WifiP2pServiceImpl(context, WifiInjector.getInstance());
        }
    
        @Override
        public void onStart() {
            Log.i(TAG, "Registering " + Context.WIFI_P2P_SERVICE);
            publishBinderService(Context.WIFI_P2P_SERVICE, mImpl);
        }
    
        @Override
        public void onBootPhase(int phase) {
            if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
                mImpl.connectivityServiceReady();
            }
        }
    }
    
    public class WifiP2pServiceImpl extends IWifiP2pManager.Stub
    public static abstract class Stub extends android.os.Binder implements android.net.wifi.p2p.IWifiP2pManager
    

    由此可得出:在系统开启wifip2p服务时会调用wifip2pManager(IWifiP2pManager service)将实现了IWifiP2pManager接口的WifiP2pServiceImpl类传给WifiP2pManager的mService,以便后续调用

    private Channel initalizeChannel(Context srcContext, Looper srcLooper, 								ChannelListener listener,Messenger messenger, Binder binder) {
            if (messenger == null) return null;//若没有成功获取WifiP2pService的引用,返回空信道构造失败
    
            Channel c = new Channel(srcContext, srcLooper, listener, binder, this);
            if (c.mAsyncChannel.connectSync(srcContext, c.mHandler, messenger)
                    == AsyncChannel.STATUS_SUCCESSFUL)
                //将处理程序同步连接到Messenger,服务器收到CMD_CHANNEL_FULL_CONNECTION请求并初始化内部实例变量以允许通信.
            {
                Bundle bundle = new Bundle();
                bundle.putString(CALLING_PACKAGE, c.mContext.getOpPackageName());
                bundle.putBinder(CALLING_BINDER, binder);
                c.mAsyncChannel.sendMessage(UPDATE_CHANNEL_INFO, 0,
                        c.putListener(null), bundle);
                return c;
            } else {
                c.close();
                return null;
            }
        }
    
    4.请求Peers设备列表并监听
    if (mManager != null) {
                mManager.requestPeers(mChannel, this);
            }
    

    监听如下:

    Activity实现PeerListListener来获取可连接peers的列表,另外一种方式就是通过广播接收者获取,逻辑见上述WiFiDirectBroadcastReceiver

    implements WifiP2pManager.PeerListListener
    @Override
        public void onPeersAvailable(WifiP2pDeviceList peers) {
            Log.d("wny", "onPeersAvailable: peers" + peers.getDeviceList().toString());
    
        }
    
    5.搜索P2p设备

    这个方法的调用是异步的,如果你设置了一个WifiP2pManager.ActionListener监听器,那么这个方法的调用结果会通过ActionListener回调返回。onSuccess()方法只告诉你该发现节点的任务执行成功,但不会提供所发现节点的任何信息。

    mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
                @Override
                public void onSuccess() {
                    Log.d("wny ", "onSuccess");
                    Toast.makeText(WifiP2PActivity.this, "检索设备", 																			Toast.LENGTH_SHORT).show();
                }//检索成功将执行onSuccess
    
                @Override
                public void onFailure(int reasonCode) {
                    Log.d("wny", "onFailure: onFailure" + reasonCode);
                    Toast.makeText(WifiP2PActivity.this, "检索设备失败", 																		Toast.LENGTH_SHORT).show();
    
                }//执行失败进入onFailure,一般检索失败是由于权限未开启导致
            });
    

    如果发现节点的任务执行成功,并且检测到了适合节点。系统会发送WIFI_P2P_PEERS_CHANGED_ACTION广播,当你收到这个广播的时候,你就可以调用requestPeers()方法,来获取发现了节点列表

    6.连接设备

    当你找到自己想要连接的设备时,你可以通过调用connect()方法来连接这个设备,调用这个方法需要一个WifiP2pConfig对象作为参数。WifiP2pConfig对象包含了进行连接的一些配置信息,包括设备地址,认证方式,谁作为GroupOwner等。这个方法调用的结果会通过WifiP2pManager.ActionListener返回

    WifiP2pConfig config = new WifiP2pConfig();
                config.deviceAddress = mDeviceAdress;//设备地址通常通过广播接收者中获取
                mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() {
                    @Override
                    public void onSuccess() {
                        //success logic
                        Log.d(TAG, "onSuccess: connect success");
                        Toast.makeText(WifiP2PActivity.this, "连接设备成功", 																	Toast.LENGTH_SHORT).show();
                    }
    
                    @Override
                    public void onFailure(int reason) {
                        //failure logic
                        Log.d(TAG, "onFailure: connect Failure");
                        Toast.makeText(WifiP2PActivity.this, "连接设备失败", 																	Toast.LENGTH_SHORT).show();
                    }
                });
    

    此方法中重写的方法只能反应连接过程是否成功,但不能确保连接是否可用,在这判断连接结果不是一个可靠的方式。

    7.接收连接信息
    @Override
        public void onConnectionInfoAvailable(final WifiP2pInfo info) {
            Log.d(TAG, "onConnectionInfoAvailable: ");
            if (info.groupFormed && info.isGroupOwner) {
                Log.d(TAG, "isGroupOwner: ");
                Log.d(TAG, info.toString().toString());
                Log.d(TAG, info.groupOwnerAddress.toString());
    
            } else if (info.groupFormed) {
                // The other device acts as the client. In this case, we enable the
                // get file button.
                Log.d(TAG, "isnotGroupOwner: ");
                Log.d(TAG, info.toString().toString());
                mDeviceIp = info.groupOwnerAddress.toString().replace("/","");
                if (mDeviceIp != null) {
                    if (socket.isConnected()) {
                        Toast.makeText(WifiP2PActivity.this,"Socket 															connected",Toast.LENGTH_SHORT).show();
                    } else {
                        Toast.makeText(WifiP2PActivity.this,"Socket 														Disconnected",Toast.LENGTH_SHORT).show();
                    }
                } else {
                    Toast.makeText(WifiP2PActivity.this,"无设备																			ip",Toast.LENGTH_SHORT).show();
                }
                Log.d(TAG, info.groupOwnerAddress.toString());
            }
        }
    

    在此方法中会先判断本机的角色(服务端/客户端),一般情况下发起连接请求的是客户端,即连接上的设备为GroupOwner,判断完后可以获取GroupOwner的ip地址,但需要注意的是直接输出的ip地址首位为“/”,需要toString().replace("/",""),到此步骤后就可以通过Socket连接上服务端,进行通信。

    8.建立Socket通讯

    一旦设备间的连接成功建立,你就可以通过socket来传输数据,基本的步骤如下。
    1、创建ServerSocket,并调用accept()方法在指定端口等待客户端的连接,注意这个过程将会阻塞线程,请在后台线程完成这个它。

    2、创建ClientSocket,然后使用服务器IP和端口去连接充当服务器的设备。

    3、 客户端往服务器发送数据。当客户端成功连接上服务器端之后,你就可以以字节流的形式向服务器发送数据了。

    4、服务器接收数据。当服务器的accept()接受一个客户端连接之后,服务器端口能够收到客户端发来的数据了。

    注意在WiFi P2P连接中,Group Owner和Group Client都能够作为Server Socket,并且当一个Socket连接建立后,双方都可以收发数据。

    下面是一个通过WiFi P2P从客户端往服务器端发送图片的例子,以下是服务器端部分代码。

    客户端连接方式:

    socket.connect((new InetSocketAddress(mDeviceIp, 8888)), 1500);
    outputStream = socket.getOutputStream();
    objectOutputStream = new ObjectOutputStream(outputStream); 
    objectOutputStream.writeObject(dataBean);//DataBean用来存储需要传输的数据信息,需要实现Serializable接口
    

    服务端接受方式:

    serverSocket = new ServerSocket(8888);
    client = serverSocket.accept();
    InputStream inputstream = client.getInputStream();
    objectInputStream = new ObjectInputStream(inputstream);
    dataBean = (DataBean) objectInputStream.readObject()
    

    最重要的是因为服务端也会用到DataBean类,所以在服务端也要编写相同的类,且这个类所在服务端和客服端的包名需要相同

    3.Tips

    • 我们在使用广播接收者的时候在暂停情况下我们需要去注销掉它
    @Override
    protected void onResume() {
        super.onResume();
        registerReceiver(mReceiver, mIntentFilter);
    }
    /* unregister the broadcast receiver */
    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver(mReceiver);
    }
    
    • 当客户端和服务端同事进行扫描时才能发现彼此,因此需要在服务端的扫描方法scanPeers()需要这样写:
    new Thread(new Runnable() {
           @Override
           public void run() {
               while (true) {
                   if(isConnect){
                       break;//当有设备连接上后结束循环,子线程自然死亡,达到停止扫描的效果
                   }
                   if (mScanState) {
                    mManager.discoverPeers(mChannel,new WifiP2pManager.ActionListener() 
                   	{
                           @Override
                           public void onSuccess() {
                               Log.d("wny ", "onSuccess");
                           }
    
                       	   @Override
                           public void onFailure(int reasonCode) {
                                Log.d("wny", "onFailure: onFailure" + reasonCode);
                           }
                     });
                   }
                   try {
                       Thread.sleep(5000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
           }
       }).start();
    

    这样写的原因是当无设备连接时就需要进行循环扫描,当连接后停止扫描,当程序结束后子线程需要自然结束。

    • DateBean中需要实现序列化的话需要成员变量都为基本类型,不能存在包装类的或者其他类的引用,所以我们需要将需要传输的文件读成Byte[]存储在DateBean中进行传输。

    • 一旦设备间的连接成功建立,你就可以通过socket来传输数据,创建ServerSocket,并调用accept()方法在指定端口等待客户端的连接,这个过程将会阻塞线程,需要在后台线程完成这个它

    public void acceptConn() throws IOException {
            client = serverSocket.accept();
            Log.d(TAG, "run: " + client.getInetAddress().getHostAddress() + "客户端已连接!");
            Message msg = Message.obtain();
            msg.what = 2;
            msg.obj = client.getInetAddress().getHostAddress() + "客户端socket已连接!";
            handler.sendMessage(msg);
            InputStream inputstream = client.getInputStream();
            objectInputStream = new ObjectInputStream(inputstream);
        }
    
        Thread acceptThread = new Thread(new Runnable() {
            boolean isEnd = false;
            @Override
            public void run() {
    
                try {
                    Log.d(TAG, "run: start serversocket");
                    serverSocket = new ServerSocket(8888);
                    Log.d(TAG, "run: accpeting");
                } catch (IOException e) {
                    e.printStackTrace();
                    Log.d(TAG, "run: start serversocket error");
                }
    
                try {
                    while (true) {
                        if(isEnd){
                            break;
                        }
                        Log.d(TAG, "run: while");
                        if (client == null) {
                            accptConn();
                            Log.d(TAG, "run: check conn");
    
                        } else {
                            if ((!client.isConnected())) {
                                accptConn();
                                Message msg = Message.obtain();
                                msg.what = 2;
                                msg.obj = "连接已断开";
                                handler.sendMessage(msg);
                            } else {
                                if (objectInputStream != null) {
                                    if (((dataBean = (DataBean) objectInputStream.readObject()) != null)) {
                                        Log.d(TAG, "run: come in");
                                        if (dataBean.getBytes() != null) {
                                            copyFile(mContext, dataBean.getBytes());
                                            Log.d(TAG, "run: accept success");
                                        }
                                    }
                                }else{
                                    Message msg = Message.obtain();
                                    msg.what = 2;
                                    msg.obj = "连接已断开";
                                    handler.sendMessage(msg);
                                }
    
                            }
                        }
                        //Log.d(TAG, "run: serverSocket accpet" + dataBean.getOrder());
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    Log.d(TAG, "Exception throws");
                }
            }
    
        });
    
    • Socket粘包处理:

    什么是粘包

    TCP有粘包现象,而UDP不会出现粘包。

    **TCP(Transport Control Protocol,传输控制协议)**是面向连接的,面向流的。TCP的收发两端都要有成对的Socket,因此,发送端为了将更多有效的包发送出去,采用了合并优化算法(Nagle算法),将多次、间隔时间短、数据量小的数据合并为一个大的数据块,进行封包处理。这样的包对于接收端来说,就没办法分辨,所以需要一些特殊的拆包机制。

    **UDP(User Datagram Protocol,用户数据报协议)**是无连接的,面向消息的提供高效率服务。不会使用合并优化算法。UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。

    粘包、拆包表现形式

    现在假设客户端向服务端连续发送了两个数据包,用packet1和packet2来表示,那么服务端收到的数据可以分为三种,现列举如下:

    第一种情况,接收端正常收到两个数据包,即没有发生拆包和粘包的现象,此种情况不在本文的讨论范围内。

    img

    第二种情况,接收端只收到一个数据包,由于TCP是不会出现丢包的,所以这一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。这种情况由于接收端不知道这两个数据包的界限,所以对于接收端来说很难处理。

    img

    第三种情况,这种情况有两种表现形式,如下图。接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,这种情况即发生了拆包和粘包。这两种情况如果不加特殊处理,对于接收端同样是不好处理的。

    img

    粘包、拆包发生原因

    发生TCP粘包或拆包有很多原因,现列出常见的几点:

    1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。

    2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。

    3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。

    4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

    等等。

    如何处理粘包

    1.提前通知接收端要传送的包的长度

    粘包问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。

    不建议使用,因为程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这样会放大网络延迟带来的性能损耗

    2.加分割标识符

    {数据段01}+标识符+{数据段02}+标识符
    发送端和接收端约定好一个标识符来区分不同的数据包,如果接收到了这么一个分隔符,就表示一个完整的包接收完毕。

    也不建议使用,因为要发送的数据很多,数据的内容格式也有很多,可能会出现标识符不唯一的情况

    3.自定义包头(建议使用)

    img

    在开始传输数据时,在包头拼上自定义的一些信息,比如前4个字节表示包的长度,5-8个字节表示传输的类型(Type:做一些业务区分),后面为实际的数据包。

    Demo:

    //发送端:
    
    package com.tcp;
    
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.Socket;
    
    public class TcpSend {
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		byte[] bt = new byte[29];
    		try {
    			// 创建连接到服务端的Socket对象
    			Socket cs = new Socket("10.168.69.177", 9999);
    			// 获取当前连接的输入流/输出流
    			InputStream din = cs.getInputStream();
    			OutputStream dout = cs.getOutputStream();
    			// 模拟一个请求消息包.
    			byte[] pack = new EncodePackage().gepPackage();
    			//发送请求消息包.
    			dout.write(pack); 
    			// 从服务器中读取数据并打印
    			din.read(bt, 0, 29);
    			if (bt[0] == 2) {
    				System.out.println("=======收到响应包=======");
    				System.out.println("包类型:" + bt[0]);
    				System.out.println("包标识:" + new String(bt, 1, 4));
    				System.out.println("包长度:" + new Integer(new String(bt, 5, 4)));
    				System.out.println("MD5校验和:" + new String(bt, 9, 16));
    				System.out.println("版本号:" + bt[25]);
    				System.out.println("协议类型:" + (byte) (bt[26]));
    				System.out.println("命令类型" + new String(bt, 27, 2));
    			}
    			// 关闭流,关闭Socket连接
    			din.close();
    			dout.close();
    			cs.close();
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    }
    
    //接收端:
    package com.tcp;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class TcpServer {
    	public static void main(String[] args) throws IOException {
    		// 声明用来计数的int局部变量
    		int count = 0;
    		byte[] head = new byte[29];
    		byte[] body;
    		try {
    			// 使用ServerSocket
    			ServerSocket server = new ServerSocket(9999);
    			// 打印提示信息
    			System.out.println("服务器端正在对端口9999进行监听");
    			// 等待客户端连接
    			while (true) {
    				// 若有连接返回对应的Socket对象。
    				Socket sc = server.accept();
    				// 获取当前链接对象的输入流
    				InputStream din = sc.getInputStream();
    				// 获取当前连接对象的输出流
    				OutputStream dout = sc.getOutputStream();
    				// 读取包头并打印包头所包含的信息
    				din.read(head);
    				System.out.println("==========================" + (++count)
    						+ "========================");
    				System.out.println("客户端IP地址:" + sc.getInetAddress());
    				System.out.println("客户端端口号:" + sc.getPort());
    				System.out.println("本地端口号:" + sc.getLocalPort());
    				System.out
    						.println("接收到的数据包16进制表示为:" + byteToHexStr(head, true));
    
    				// 判断,如果是请求包则返回一个响应
    				if (head[0] == 1) {
    					//临时代码,接收请求包并原样返回消息包头
    					head[0] = 2;
    					dout.write(head);
    					System.out.println("******收到请求包,已返回响应......******");
    				} else if (head[0] == 2) {
    					System.out.println("************收到响应包****************");
    				} else {
    					System.out.println("*******数据包已损坏,接收失败************");
    				}
    				// 关闭流
    				din.close();
    				dout.close();
    				// 关闭Socket连接
    				sc.close();
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    	//该方法将数据转为16进制显示
    	public static String byteToHexStr(byte[] bArray, boolean format) {
    		StringBuffer strb = new StringBuffer(bArray.length);
    		String str;
    		for (int i = 0; i < bArray.length; i++) {
    			str = Integer.toHexString(0xFF & bArray[i]).trim();
    			if (str.length() < 2){
    				str = "0" + str;
    			}
    			if (format){
    				str += " ";
    			}
    			strb.append(str);
    		}
    		str = strb.toString().toUpperCase().trim();
    		return str;
    	}
    }
    
    • 动态广播在activity 的onResume里注册,onPause里注销。

    大家都知道,activity的生命周期方法基本上是成对出现的,例如onCreate对应onDestory,onStart对应onStop,onResume对于onPause。

    对于动态广播来说,有注册必然得有注销,这也得成对出现。重复注册注销或者注册忘了注销这都不行,后者会报Are you missing a call to unregisterReceiver()?错误,虽然不至于让应用奔溃,但是会导致内存泄露。

    那么为什么不在onCreate和onDestory或onStart和onStop,注册注销呢?那是因为考虑到,当系统因为内存不足要回收activity占用的资源时,有些生命周期方法(onStop,onDestory)可能不会执行。

    看下官方对于activity生命周期的解释:

    1.先看生命周期图,注意红色矩形框部分可以发现:当其他优先级更高的应用需要内存时,activity在执行完onPause方法以后就会被销毁,那么onStop,onDestory方法就会不执行,当再回到这个activity时,就会从onCreate方法开始执行。

    img

    1. 看下对生命周期方法的描述,onPause被标记为可以killable的在3.0以前,而onStop,onDestory也同样标记为可以killable的。

    img

    img

    img

    img

    img

    3.对于killable的解释,意思大概是onPause执行完以后,activity就有可能会被销毁,所以应该利用onPause以及onSaveInstanceState方法来保存必要的数据。

    4.看官方的特别声明,在3.0以后,应用不会处于killable状态直到onStop返回,onStop以及onSaveInstanceState会被安全地调用。

    3.0以后,综合1、2、3点,onPause方法执行完以后,该activity有可能会被系统回收,所以后续的生命周期方法onStop,onDestory不会被执行;而4证明onStop执行完以后,该activity才有可能会被系统回收,所以后续的生命周期方法onDestory不会被执行。感觉官方对于activity被回收的时机有矛盾的地方。

    但是我觉得根据1,2,3点得出的结论(在onResume注册广播,在onPause注销广播),来注册销毁广播比较保险,因为onPause必然会被执行,而且当activity处于onPause时,焦点已经不在了,理论上那就可以不用接收广播了。

    结论:对于动态广播,在onResume注册广播,在onPause注销广播。

    展开全文
  • androidwifi连接功能开发,踩过的坑

    千次阅读 2019-05-13 11:52:57
    前段时间做的一个小项目,是关于wifi连接功能的,在此做个小总结,借鉴了网上很前辈的开发经验,所以这里分享一下,欢迎大家沟通交流。我们使用的google公开给第三方app使用的接口,基本全部连接功能都是使用...
  • Android WiFi —softAP流程分析

    千次阅读 2022-04-25 20:56:12
    Android WiFi — Ap功能实现与源码分析0. 前言1. wifiAp的ip2. WifiAp的config分析2.1 默认的config2.2 修改wifiAp的config配置流程3. 开启/关闭WifiAp热点状态流程4. 已连接设备列表4.1 读取wifiAp的已连接设备列表...
  • WIFI 网络开发过程中,容易对网络变化的系统广播和不同广播对应的网络状态区分不清楚,所以对常用的系统网络广播Intent Action和网络状态含义做了整理,希望能够加强记忆并且帮助到大家理解何时应该监听哪种系统...
  • 为 SCAN_RESULTS_AVAILABLE_ACTION 注册一个广播监听器,系统会在完成扫描请求时调用此监听器,提供其成功/失败状态。对于搭载 Android 10(API 级别 29)及更高版本的设备,系统将针对平台或其他应用在设备上执行的...
  • Android -- Wifi连接流程分析

    千次阅读 2017-05-10 14:02:43
    Android -- Wifi连接流程分析 当我们在Android手机上通过Settings连接一个AP时,间接调用WifiManager的connect()方法: [java] view plain copy /** * Connect to a network with the ...
  • 为了调试一个bug的时候突然发现app显示无网络,关掉app打开自带浏览器尝试打开百度,发现打不开,前往模拟器设置页面提示WiFi受限无法上网,果断开始百度,一搜出来一大堆文章按寻思应该可以了,一个个点过去发现全...
  • 第三章 将源码导入Android Studio(无需编译idegen) 文章目录系列文章目录前言一、WiFi是什么?二、WiFi系统架构1. 概述2. 应用框架3. Wi-Fi 服务4. Wi-Fi HAL三、WiFi面向应用提供的API1. WLAN 扫描2. WLAN 直连...
  • 好吧,我终于弄明白了,我...WiFiConfig在中注册的WiFiConfig文件表viaWiFiConfigManager.addNetwork().WifiConfigManager.addNetwork(). 我假设如果那行动成功了(即没有返回-1allowedAuthAlgorithmsallowedProtocol...
  • QtScrcpyQtScrcpy可以通过USB(或通过TCP/IP)连接Android设备,并进行显示和控制。不需要root权限。单个应用程序最多支持16个安卓设备同时连接。同时支持GNU/Linux,Windows和MacOS三大主流桌面平台它专注于:精致 (仅...
  • WifiConfigurationandroid.net.wifi.WifiConfigurationpublic class WifiConfiguration extends Object implements ParcelableWifiManager android.net.wifi.WifiManagerpublic class WifiManager extends ...
  • 使用开始wifi扫描的代码很简单:val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManagerval success = wifiManager.startScan()if (!success) {// scan failure handlingscanFailure()}...
  • 在上一篇中我们介绍了WiFi热点的创建和关闭,如果你还没阅读过,建议先阅读上一篇文章Android WiFi开发教程(一)——WiFi热点的创建与关闭。 本章节主要继续介绍WiFi的搜索和连接WiFi的搜索 /* 搜索wifi热点 ...
  • 简介:Wi-Fi Direct技术的目的是在没有Wi-Fi AP的情况下由两个或者个Wi-Fi设备互相之间进行高速的数据通信。通信完全基于TCP/IP 协议,因此对于开发基于Wi-Fi Direct的应用来说非常友好。 Wi-Fi Direct在刚提出时...
  • RK3399 实现内外网共存补丁: (注:基于rk3399已经...diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index ...
  • 实现方法:使用android的server(服务)功能,使用线程不断检测wifi连接情况,如果断开wifi就立即签离。同样要保证服务不要被杀死。 3、还有其他问题后续再更新,希望大家有什么意见与建议多多提出!谢谢! 项目文件...
  • 现有的智能手机是可以充当Wifi摄像头来使用的,这就需要装一个App就能实现了,如果是用别的下载来APP安装用来会不会不放心呢,如果自己有能力,那就可以通过开发Android App项目过程来实现视频监控,有兴趣的来看看...
  • Android WiFi Display (Miracast)

    千次阅读 2020-04-13 08:50:44
    WiFi Direct:WiFi P2P,支持在没有AP(Access Point)下,WiFi设备直连并通信 WiFi Protected Setup:用于用户自动配置WiFi网络、添加WiFi设备 11n/WMM/WPA2:11n是802.11n协议(56M提升至600M);WMM是WiFi ...
  • 基于Android P源码学习; 代码片为了方便阅读段经过删、裁减,请以实际源码为准; 入口 根据前两篇的分析结果,调用栈都汇聚到了WifiNative: 打开Wifi: mWifiNative.setupInterfaceForClientMode(false, ...
  • 实战Android Wifi P2p

    千次阅读 2018-12-12 16:03:56
    在我们的应用设计中,有这么一个需求,将一台已连接无人机的Android手机(主机)的图传发送给另一台手机(从...Android有一种连接方式叫 Wi-Fi点对点(P2P),他不需要组织局域网环境,在手机两端打开wifi就可以搜索到...
  • Android WiFi开发教程

    万次阅读 2018-10-11 09:27:12
    Android WiFi开发教程(一)——WiFi热点的创建与关闭   相对于BlueTooth,WiFi是当今使用最广的一种无线网络传输技术, 几乎所有智能手机、平板电脑和笔记本电脑都支持Wi-Fi上网。因此,掌握基本的WiFI开发技术是...
  • Android Q wifi connect

    千次阅读 2020-03-20 15:03:06
    wifi连接流程 ->wifi连接过程中app可见的操作流程: 列表中选择一个可用AP -> 弹出对话框 -> 输入AP密码点击保存 -> 底层处理连接流程 ->底层处理概述 WifiStateMachine处理SAVE_NETWORK消息 -&...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,350
精华内容 2,140
关键字:

android wifi多次连接失败