深入理解Android:Wi-Fi模块、NFC和GPS卷(txt+pdf+epub+mobi电子书下载)


发布时间:2020-07-30 19:14:42

点击下载

作者:邓凡平

出版社:机械工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

深入理解Android:Wi-Fi模块、NFC和GPS卷

深入理解Android:Wi-Fi模块、NFC和GPS卷试读:

前言

本书主要内容及特色

本书所讲解的Wi-Fi、NFC以及GPS模块的背后都涉及非常多的专业知识,例如与Wi-Fi相关的802.11协议、Wi-Fi Alliance(Wi-Fi联盟)定义的Wi-Fi Simple Configuration和Wi-Fi P2P协议、NFC Forum定义的一整套与NFC相关的协议、与GPS相关的卫星导航原理、AGPS和OMA-SUPL协议等。显然,如果不了解这些专业知识,就不可能真正掌握它们在Android平台中的代码实现。

考虑到这些专业知识的重要性,本书在讲解Android平台中Wi-Fi、NFC和GPS模块的实现之前,先重点介绍与代码相关的专业知识。当然,这些专业知识内容如此丰富,在一本书中无法全部涵盖。为了方便读者进一步深入学习,本书每章的最后都会列举笔者在撰写各章时所阅读的参考资料。

以下是本书的内容概述。

·第1章介绍本书的内容组成、使用的工具以及参考源码的下载方法。

·第2章介绍Netd以及相关的背景知识。

·第3章介绍Wi-Fi基础知识。Wi-Fi是本章的重点,而且也是当下最热门的技术。

·第4章介绍wpa_supplicant,它是Wi-Fi领域中最核心的软件实现。

·第5章介绍WifiService,它是Android平台中特有的Wi-Fi服务模块。

·第6章和第7章介绍Wi-Fi Alliance推出的两项重要技术——Wi-Fi Simple Configuration和Wi-Fi P2P,以及它们在Android平台中的代码实现。

·第8章介绍NFC背景知识以及NFC在Android平台中的代码实现。NFC也是历史比较悠久的技术,希望它能随着Android的普及而走向大众。

·第9章介绍GPS原理及Android平台中的位置管理服务架构。

·附录为笔者和审稿专家之一的吴劲良先生关于本书定位、学习方法等方面的讨论。相信这些讨论内容能引起读者的共鸣。

本书通过理论和代码相结合的方式进行讲解,旨在引领读者一步步了解Wi-Fi、NFC和GPS模块的工作原理。总之,笔者希望读者在阅读完本书后能有以下收获。

·初步掌握Wi-Fi、NFC和GPS的专业知识。

·根据其实现代码,进一步加深对这些专业知识的理解。

读者对象

适合阅读本书的读者包括:

·Android系统开发工程师

系统开发工程师常常需要深入理解系统的运转过程,而本书所涉及的内容正是他们在工作和学习中最想了解的。对具体模块感兴趣的读者也可单刀直入,阅读相关章节。

·Wi-Fi、NFC或GPS的BSP开发工程师

BSP开发工程师更需要对Android平台中这些模块的工作原理及背景知识有深入的理解。虽然本书没有介绍这些模块在Linux Kernel层的实现,但了解它们在用户空间的工作流程也将极大帮助BSP开发工程师拓展自己的知识面。

·对Wi-Fi、NFC和GPS感兴趣的在校高年级本科生、研究生和其他读者

在掌握理论的基础上,如何在实际代码中来实现或使用它们也许是众多学子最想知道的。希望这本理论与代码实现深度结合的书籍会助您一臂之力。

如何阅读本书

本书是一本专业知识和代码实现相结合的书籍,所以读者在阅读时应注意以下事项。

·首先阅读专业知识。如果对这些内容比较了解,可以直接跳转到代码实现。

·然后是Android平台中相关模块的代码实现。这些代码实现往往基于一定的专业知识,所以在阅读代码时务必和前述的专业知识相结合。

·每章最后都列出了笔者在撰写各章时所参考的资料。资料较多,读者可根据这些内容开展进一步的研究工作。

·每章开头都把本章涉及的源码路径全部列出,而在具体分析源码时,只列出该源码的文件名及所分析的函数或相关数据结构名。例如:

[-->AndroidRuntime.cpp::函数或数据结构名]//源码分析和一些注释

最后,本书在描述类之间的关系及函数调用流程上,使用了UML的静态类图及序列图。UML是一个强大的工具,但它的建模规范过于烦琐,为更简单清晰地描述事情的本质,本书并未完全遵循UML的建模规范。如图1所示,外部类内部的方框用于表示内部类。另外,“外部类A.内部类B”也用于表示内部类。接口和普通类用同一种框图表示。图1 类图

图2所示为本书描述数据结构及成员时使用的UML图例。图2 数据结构图

特别注意 本书所使用的UML图都比较简单,读者不必花费大量时间专门学习UML。另外,出于方便考虑,本书所绘制的UML图没有严格遵守UML规范,这一点敬请读者谅解。

本书涉及的Android源码及一些开发工具的下载地址为http://115.com/lb/5lbdugrdt4r。关于它们的使用详情,请读者阅读1.3节。

勘误和支持

由于作者的水平有限,加之编写时间仓促,书中难免会出现一些错误或不准确的地方,恳请读者不吝批评指正。若有问题,可通过邮箱或在博客上留言与笔者共同商讨。笔者的联系方式如下。

·邮箱:fanping.deng@gmail.com

·博客:blog.csdn.net/innost和http://my.oschina.net/innost/blog

致谢

首先要感谢杨福川编辑的大力支持。另外,要感谢本书审稿编辑白宇严谨负责的工作。

特别感谢Tieto公司。Tieto开放的企业文化、Android团队高效的工作效率、团队成员之间默契的工作配合程度,以及领导无私而有力的支持着实让我感到幸运和自豪。在Tieto就职的一年中,笔者所在的Android团队不仅成功赢得了客户的信任,更是得到了Tieto公司总部和其他国家分公司同事们的一致认可。同时,团队成员还积极分享,并在《程序员》杂志上发表了六篇高质量的文章。

在此,笔者借助本书对Tieto的领导和同事表示衷心的感谢。他们是中国北京分公司的Leo、hongbin、James、yantao、meiyang、dujiang、changgeng、caimin、wenjing、huaizhi、huirong、xinzhi、huimin、yuzheng、Liuxuan、Emily、Diego、jinghua、Jenny等,中国成都分公司的tianxiang、chengguo等,波兰分公司的Marcin、Marciej、Filip Matusiak等、捷克分公司的Vaclav、Bronislav、Petrous Jan等、芬兰分公司的Mikel Echegoyen。

当然,本书能得以快速出版,还需要感谢两位功力深厚并热心参与技术审稿的专家。他们是全志(Allwinner)公司Wireless Team负责人吴劲良,以及高通(Qualcomm)中国资深研发经理杨洋。二位专家在各自领域所表现出来的专业素养和技术水平,时刻提醒笔者应牢记“路漫漫其修远兮,吾将上下而求索”。另外,高通中国资深研发经理毛晓冬也对本书成功编写提供了帮助,在此一并表示感谢。

最后,感谢我的家人,尤其是我的妻子。希望明年上天能恩赐一个健康可爱的宝宝,这样,我将拥有更加无穷的动力编写更多书籍来回馈花费宝贵时间和精力关注本书的读者,以及所有在人生和职业道路上曾给予我指导的诸位师长。

第1章 准备工作

本章主要内容

·本书的内容组成;

·工具使用;

·本书资源下载说明。

1.1 Android系统架构

Android是Google公司推出的一款手机开发平台。该平台本身是基于Linux内核的,图1-1展示了这个系统的架构。图1-1 Android系统架构

从图1-1可知,Android系统大体可分为四层,从下往上依次如下。

·Linux内核层,目前Android 4.4(代号为KitKat)基于Linux内核3.4版本。

·Libraries层,这一层提供动态库(也叫共享库)、Android运行时①库、Dalvik虚拟机等。从编程语言方面来说,这一层大部分都是用C或C++写的,所以也可以简单地把它看成是Native层。

·Libraries层之上是Framework层,这一层大部分用Java语言编写。它是Android平台上Java世界的基石。

·Framework层之上是Applications层,和用户直接交互的就是这些应用程序,它们都是用Java开发的。① 4.4版本新增了ART虚拟机运行时,相信它的出现能提升应用程序的运行速度。

1.2 工具使用

本节介绍Android开发和源码研究过程中的三件利器。

1.2.1 Source Insight的使用

Source Insight是阅读源码的必备工具,是一个Windows下的软件,在Linux平台上可通过wine安装。下面介绍一下如何在Source Insight中导入源码。

使用Source Insight时,需要新建一个源码工程,通过菜单项Project→New Project,可指定源码的目录。

提示 如果把Android所有源代码都加到工程中,将导致Source Insight运行速度非常慢。

实际上,只需要将当前分析的源码目录加到工程即可。例如,新建一个Source Insight工程后,只把源码/framework/base目录加进去了。另外,当一个目录下的源码分析完后,可以通过Project→Add and Remove Project Files选项把无须分析的目录从工程中去掉。上述步骤如图1-2所示。图1-2 添加或删除工程中的目录

从图1-2右边的框可知,Source Insight支持动态添加或删除目录。通过这种方式可极大减少Source Insight的工作负担。

提示 一般首先把framework/base下的目录加到工程,以后如有需要,再把其他目录加进来。另外,关于Source Insight其他使用技巧,可参考《深入理解Android:卷Ⅰ》第1章。

1.2.2 Eclipse的使用

笔者一般使用Source Insight来查看Native代码,而Android推荐的集成开发工具Eclipse既能查看Java代码和Native代码,也能调试系统核心进程。

1.导入Android Framework Java源码

注意,这一步必须编译整个Android源码才可以实施,步骤如下。

1)将Android源码目录/development/ide/eclipse/.classpath复制到Android源码根目录。

2)打开Android源码根目录下的.classpath文件。该文件是供Eclipse使用的,其中保存的是源码目录中各个模块的路径。

由于我们只关心Framework相关的模块,因此可以把一些不是Framework的目录从该文件中注释掉。同时,去掉不必要的模块也可加快Android源码导入速度。图1-3所示为该文件的部分内容。图1-3 .classpath文件内容

然后,单击Eclipse菜单栏New→Java Project,弹出如图1-4所示的对话框。设置Location为Android 4.2源码所在路径。图1-4 导入Android源码

由于Android 4.2源码文件较多,导入过程会持续较长一段时间。

提示 导入源码前一定要取消Eclipse的自动编译选项(通过菜单栏Project→Build Project Automatically设置)。另外,源码导入完毕后,千万不要清理(clean)这个工程。清理会删除之前源码编译所生成的文件,导致后续又要重新编译Android系统了。

本书的共享资源中提供了一个已经配置好的.classpath文件,供读者下载并使用。

2.导入Android Native代码

本节介绍如何在Eclipse中导入Android Native代码,其步骤如下所示。

导入Android Framework中的Java文件后,首先切换到C/C++视图(通过单击菜单栏Window→Open Perspective→选择C/C++)。

单击菜单栏New→C/C++,然后选择Convert to a C/C++project,如图1-5所示。

按照图1-5操作之后,之前的Java工程就被转成C++工程。不过读者仍需要完成以下几个步骤。

1)通过Properties→C/C++General→Paths and symbols,打开该工程的路径和符号设置对话。如图1-6所示。

2)在图1-6上边的框中选择Includes页面,然后单击下方框中的Import Settings,选择4.2源码/development/ide/eclipse/android-include-paths.xml以导入路径配置。

3)同上,选择图1-6中的Symbols页面,然后通过下方的Import Settings导入4.2源码/development/ide/eclipse/android-symbols.xml文件。图1-5 转换成C++工程图1-6 路径和符号设置

配置好路径和符号文件后,可进一步通过图1-6中的Source Location页面选择此次需要导入的C++文件,如图1-7所示。图1-7 过滤C++文件

通过图1-7中Add Folder选项,可以选择哪些目录下的C++文件被过滤掉。笔者目前仅导入了frameworks目录下的Native代码。

当所有文件都导入完毕后,读者通过右击C++工程,然后选择Index→Rebuild来重新生成Native代码的索引。

通过上述方法就能导入Android中的Native代码了。

3.调试SystemServer

调试SystemServer的步骤如下。

1)首先编译Android源码工程。编译过程中会有很多警告,如果有错误,大部分原因是.classpath文件将不需要的模块包含进来,可根据Eclipse的提示做相应处理。笔者配置的几台机器基本都是一次配置就成功了。

2)在Android源码工程上单击右键,依次单击Debug As→Debug Configurations,弹出如图1-8所示的对话框,然后从左边找到Remote Java Application一栏。

3)单击图1-8中黑框所示的新建按钮,按图1-9中的内容设置该对话框。图1-8 Debug配置图1-9 Remote Java Application配置

如图1-9所示,需要选择Remote调试端口号为8600,Host类型为localhost。8600是SystemServer进程的调试端口号。Eclipse一旦连接到该端口,即可通过JDWP协议来调试SystemServer。

提示 读者可阅读《深入理解Android:卷Ⅱ》第1章了解更多使用Eclipse的建议。

1.2.3 BusyBox的使用

BusyBox,号称Linux平台上的“瑞士军刀”,它提供了很多常用的工具,例如grep、find等。这些工具在标准Linux上都有,但Android系统却去掉了其中的大多数工具。这导致了我们在调试程序、研究Android系统时步履维艰,所以需要在手机上安装BusyBox。

1.下载BusyBox

可从网站http://www.busybox.net/downloads/binaries/1.21.1/下载已编译好的BusyBox,如图1-10所示。图1-10 BusyBox下载

注意,该网站已经根据不同平台编译好对应的BusyBox,可根据自己手机的情况下载对应的文件。笔者下载了支持Galaxy Note 2的busybox-armv7l。

提升:arm v7表示ARM指令集为v7,目前ARM Cortex-A8/A9系列的CPU支持该指令集。

2.安装和使用BusyBox

下载完BusyBox后,需使用adb push命令将它安装到手机上。如:adb push busybox /system/xbin #为了避免冲突,笔者push到了/system/xbin目录下cd /system/xbin #进入对应目录chmod 755 busybox #更改busybox权限为可执行busybox –-install . #安装busyboxgrep #执行busybox提供的grep命令,或者busybox xxx执行xxx命令也行

BusyBox安装完,如果执行busybox命令,就会打印如图1-11所示的输出。图1-11 BusyBox提供的工具

从上图中可看出,BusyBox提供了不少的工具,这样,我们在研究Android系统时就如虎添翼了。

提示 本书共享资源中提供了busybox-armv7l的下载。

1.3 本书资源下载说明

为了减轻国内读者无法从Android官网下载源码的烦恼,笔者在115网盘上分享了本书所使用的Android源码及其他一些资源,如图1-12所示。图1-12 本书资源

图1-12中所示:

·4.2.2.tar.gz为4.2.2源码压缩包。请读者特别注意,本书对wpa_supplicant的分析使用的是Android 4.1源码中的wpa_supplicant,故笔者在external目录中将4.1版本中的wpa_supplicant代码复制到wpa_supplicant_8_4.1中。

·classpath为Android Java配置文件,使用时请将它改名为".classpath"。

·busybox-armv7l是BusyBox。

·com.cb.eclipse.folding_1.0.6.jar是一个Eclipse的插件,名叫①Coffee Byte Java,它可以折叠代码段以方便阅读。

最后四个文件为笔者研究Wi-Fi时,利用Wi-Fi数据截获工具AirPcap保存的相关协议数据包,其中p2p_cap为测试P2P时保存的数据包,wps_pbc和wps_pin为测试WSC PBC和PIN时保存的数据包,wpa_supplicant_analysis为测试STA加入AP时所保存的数据包。没有AirPcap工具的读者可通过Wireshark工具直接导入这些数据以更直观的方式来分析Wi-Fi。该资源的下载地址是http://115.com/lb/5lbdugrdt4r。另外,请读者务必关注笔者的博客blog.csdn.net/innost以获取更新信息。① 详细用法请参考《深入理解Android:卷Ⅱ》第1章。

第2章 深入理解Netd

本章所涉及的源代码文件名及位置

·main.cpp  system/netd/main.cpp

·NetlinkManager.cpp  system/netd/NetlinkManager.cpp

·NetlinkHandler.cpp  system/netd/NetlinkHandler.cpp

·CommandListener.cpp  system/netd/CommandListener.cpp

·DnsProxyListener.cpp  system/netd/DnsProxyListener.cpp

·MDnsSdListener.cpp  system/netd/MDnsSdListener.cpp

·getaddrinfo.c  bionic/libc/netbsd/net/getaddrinfo.c

·dns_sd.h  external/mdnsresponder/mDNSShared/dns_sd.h

·ifc_utils.c  system/core/libnetutils/ifc_utils.c

·ndc.c  system/netd/ndc.c

·SecondaryTableController.cpp  system/netd/SecondaryTableController.cpp

·InterfaceController.cpp  system/netd/InterfaceController.cpp

·FirewallController.cpp  system/netd/FirewallController.cpp

·logwrap.c  system/netd/logwrap.c

·TetherController.cpp  system/netd/TetherController.cpp

·SoftapController.cpp  system/netd/SoftapController.cpp

·SystemServer.java  framework/base/services/java/com/android/server/SystemServer.java

·NetworkManagementService.java  framework/base/services/java/com/android/server/NetworkManagementService.java

2.1 概述

Netd是Android系统中专门负责网络管理和控制的后台daemon程序,其功能主要分三部分。

·设置防火墙(Firewall)、网络地址转换(NAT)、带宽控制、无线网卡软接入点(Soft Access Point)控制,网络设备绑定(Tether)等。

·Android系统中DNS信息的缓存和管理。

·网络服务搜索(Net Service Discovery,NSD)功能,包括服务注册(Service Registration)、服务搜索(Service Browse)和服务名解析(Service Resolve)等。

Netd的工作流程和Vold类似,其工作可分成两部分。

·Netd接收并处理来自Framework层中NetworkManagementService或NsdService的命令。这些命令最终由Netd中对应的Command对象去处理。

·Netd接收并解析来自Kernel的UEvent消息,然后再转发给Framework层中对应Service去处理。

由上述内容可知,Netd位于Framework层和Kernel层之间,它是Android系统中网络相关消息和命令转发及处理的中枢模块。Netd的代码量不大,难度较低,但其所涉及的相关背景知识却比较多。本章对Netd的分析将从以下几个方面入手。

·首先介绍Netd的大体工作流程以及DNS、MDns相关的背景知识。①关于Netd的工作流程分析,读者也可参考其他资料。

·本章集中介绍Netd中涉及的Android系统中网络管理和控制的相关工具。它们是iptables、tc和ip。

·然后介绍Netd中CommandListener的命令处理。这些命令的正常工作依赖于前面介绍的iptables等工具。

·最后,介绍Java Framework中的NetworkManagementService服务。

提示 NsdService比较简单,感兴趣的读者不妨阅读作者的一篇博文"Android Says Bonjour"中的2.2节“NsdService介绍”。地址位于http://blog.csdn.net/innost/article/details/8629139。① 可参考《深入理解Android:卷Ⅰ》第9章关于Vold的分析。

2.2 Netd工作流程

Netd进程由init进程根据init.rc的对应配置项而启动,其配置项如图2-1所示。

由图2-1可知,Netd启动时将创建三个TCP监听socket,其名称分别为netd、dnsproxyd和mdns。图2-1 Netd启动配置参数

根据本章后续分析,读者将会看到以下内容。

·Framework层中的NetworkManagementService和Nsd-Service将分别和netd及mdns监听socket建立链接并交互。

·每一个调用和域名解析相关的socket API(如getaddrinfo或gethostbyname等)的进程都会借由dnsproxyd监听socket与netd建立链接。

下面开始分析Netd进程。① 关于init工作原理以及init.rc的分析方法,可参考《深入理解Android:卷Ⅰ》第3章关于init进程的分析。

2.2.1 main函数分析

Netd进程的入口函数是其main函数,代码如下所示。

[-->main.cpp]int main() { CommandListener *cl; NetlinkManager *nm; DnsProxyListener *dpl; MDnsSdListener *mdnsl; ALOGI("Netd 1.0 starting"); // 为Netd进程屏蔽SIGPIPE信号 blockSigpipe(); // ①创建NetlinkManager nm = NetlinkManager::Instance(); // ②创建CommandListener,它将创建名为"netd"的监听socket cl = new CommandListener(); // 设置NetlinkManager的消息发送者(Broadcaster)为CommandListener。 nm->setBroadcaster((SocketListener *) cl); // 启动NetlinkManager nm->start(); ...... // 注意下面这行代码,它为本Netd设置环境变量ANDROID_DNS_MODE为"local",其作用将在2.2.4节介绍 setenv("ANDROID_DNS_MODE", "local", 1); // ③创建DnsProxyListener,它将创建名为"dnsproxyd"的监听socket dpl = new DnsProxyListener(); dpl->startListener(); // ④创建MDnsSdListener并启动监听,它将创建名为"mdns"的监听socket mdnsl = new MDnsSdListener(); mdnsl->startListener(); cl->startListener(); while(1) { sleep(1000); } exit(0);}

Netd的main函数非常简单,主要是创建几个重要成员并启动相应的工作,这四个重要成员分别如下。

·NetlinkManager:接收并处理来自Kernel的UEvent消息。这些消息经NetlinkManager解析后将借助它的Broadcaster(也就是代码中为NetlinkManager设置的CommandListener)发送给Framework层的NetworkManagementService。

·CommandListener、DnsProxyListener、MDnsSdListener:分别创建名为"netd"、"dnsproxyd"、"mdns"的监听socket,并处理来客户端的命令。

下面将分别讨论这四位成员的作用。

2.2.2 NetlinkManager分析

NetlinkManager(以后简称NM)主要负责接收并解析来自Kernel的UEvent消息。其核心代码在start函数中,如下所示。

[-->NetlinkManager.cpp::start]int NetlinkManager::start() { // 创建接收NETLINK_KOBJECT_UEVENT消息的socket,其值保存在mUeventSock中 // 其中,NETLINK_FORMAT_ASCII代表UEvent消息的内容为ASCII字符串 mUeventHandler = setupSocket(&mUeventSock, NETLINK_KOBJECT_UEVENT, 0xffffffff, NetlinkListener::NETLINK_FORMAT_ASCII); // 创建接收RTMGPR_LINK消息的socket,其值保存在mRouteSock中 // 其中,NETLINK_FORMAT_BINARY代表UEvent消息的类型为结构体,故需要进行二进制解析 mRouteHandler = setupSocket(&mRouteSock, NETLINK_ROUTE, RTMGRP_LINK, NetlinkListener::NETLINK_FORMAT_BINARY); // 创建接收NETLINK_NFLOG消息的socket,其值保存在mQuotaSock中 mQuotaHandler = setupSocket(&mQuotaSock, NETLINK_NFLOG, NFLOG_QUOTA_GROUP, NetlinkListener::NETLINK_FORMAT_BINARY); return 0;}

NM的start函数主要是向Kernel注册三个用于接收UEvent事件的[1][2]socket,这三个UEvent分别对应于以下内容。

·NETLINK_KOBJECT_UEVENT:代表kobject事件,由于这些事件包含的信息由ASCII字符串表达,故上述代码中使用了NETLINK_FOMRAT_ASCII。它表示将采用字符串解析的方法去解析接收到的UEvent消息。kobject一般用来通知内核中某个模块的加载或卸载。对于NM来说,其关注的是/sys/class/net下相应模块的加载或卸载消息。

·NETLINK_ROUTE:代表Kernel中routing或link改变时对应的消息。NETLINK_ROUTE包含很多子项,上述代码中使用了RTMGRP_LINK项。二者结合起来使用,表示NM希望收到网络链路断开或接通时对应的UEvent消息(笔者在Ubuntu PC上测试过,当网卡上拔掉或插入网线时,会触发这些UEvent消息的发送)。由于对应UEvent消息内部封装了nlmsghdr等相关结构体,故上述代码使用了NETLINK_FORMAT_BINARY来指示解析UEvent消息时将使用二进制的解析方法。

·NETLINK_NFLOG:和2.3.6节介绍的带宽控制有关。Netd中的带宽控制可以设置一个预警值,当网络数据超过一定字节数就会触发Kernel发送一个警告。该功能属于iptables的扩展项,但由于iptables的文档更新速度较慢(这也是很多开源项目的一大弊端),笔者一直未能找到相关的正式说明。值得指出的是,上述代码中有关NETLINK_NFLOG相关socket的设置并非所有Kernel版本都支持。同时,NFLOG_QUOTA_GROUP的值是直接定义在NetlinkManager.cpp中的,而非和其他类似系统定义一样定义在系统头文件中,这也表明NFLOG_QUOTA_GROUP的功能比较新。

提示 读者可通过在Linux终端中执行man PF_LINK得到有关NETLINK的详细说明。

上述start函数将调用setupSocket创建用于接收UEvent消息的socket以及一个解析对象NetlinkHandler。setupSocket代码本身比较简单,此处就不展开分析。

下面来看NM及其家族成员,它们之间的关系如图2-2所示。图2-2 NM家族成员

由图2-2可知:

·NetlinkHandler和CommandListener均间接从SocketListener派生。其中,NetlinkHandler收到的socket消息将通过onEvent回调处理。

·结合前文所述,NetlinkManager分别注册了三个用于接收UEvent的socket,其对应的NetlinkHandler分别是mUeventHandler、mRouteHandler和mQuotaHandler。

·NetlinkHandler接收到的UEvent消息会转换成一个NetlinkEvent对象。NetlinkEvent对象封装了对UEvent消息的解析方法。对于NETLINK_FOMRAT_ASCII类型,其parseAsciiNetlinkMessage函数会被调用,而对于NETLINK_FORMAT_BINARY类型,其parseBinaryNetlinkMessage函数会被调用。

·NM处理流程的输入为一个解析后的NetlinkEvent对象。NM完成相应工作后,其处理结果将经由mBroadcaster对象传递给Framework层的接收者,也就是NetworkManagementService。

·CommandListener从FrameworkListener派生,而FrameworkListener内部有一个数组mCommands,用来存储注册到FrameworkListener中的命令处理对象。

下面简单了解NetlinkHandler的onEvent函数,由于其内部已针对不同属性的NetlinkEvent进行了分类处理,故浏览这段代码能对前文所述不同UEvent消息的作用加深理解。

[-->NetlinkHandler.cpp::onEvent]void NetlinkHandler::onEvent(NetlinkEvent *evt) { const char *subsys = evt->getSubsystem(); ...... // 处理对应NETLINK_KOBJECT_UEVENT和NETLINK_ROUTE的信息 if (!strcmp(subsys, "net")) { int action = evt->getAction(); const char *iface = evt->findParam("INTERFACE"); // 查找消息中携带的网络设备名 if (action == evt->NlActionAdd) { notifyInterfaceAdded(iface);// 添加NIC(Network Interface Card)的消息 } else if (action == evt->NlActionRemove) { notifyInterfaceRemoved(iface); // NIC被移除的消息 } else if (action == evt->NlActionChange) { evt->dump(); notifyInterfaceChanged("nana", true); // NIC变化消息 } else if (action == evt->NlActionLinkUp) {// 下面两个消息来自NETLINK_ROUTE notifyInterfaceLinkChanged(iface, true); // 链路启用(类似插网线) } else if (action == evt->NlActionLinkDown) { notifyInterfaceLinkChanged(iface, false); // 链路断开(类似拔网线) } } else if (!strcmp(subsys, "qlog")) { // 对应NETLINK_NFLOG const char *alertName = evt->findParam("ALERT_NAME"); const char *iface = evt->findParam("INTERFACE"); notifyQuotaLimitReached(alertName, iface);// 当数据量超过预警值,则会收到该通知 } else if (!strcmp(subsys, "xt_idletimer")) { // 这和后文的idletimer有关,用于跟踪某个NIC的工作状态,即"idle"或"active" // 检测时间按秒计算 int action = evt->getAction(); const char *label = evt->findParam("LABEL"); const char *state = evt->findParam("STATE"); if (label == NULL) { label = evt->findParam("INTERFACE"); } if (state) notifyInterfaceClassActivity(label, !strcmp("active", state)); } ......}

由上边代码可知,NETLINK_KOBJECT_UEVENT和NETLINK_ROUTE主要反映网络设备的事件和状态,包括NIC的添加、删除和修改,以及链路的连接状态等。NETLINK_NFLOG用于反映设置的log是否超过配额。另外,上边代码中还处理了xt_idletimer的uevent消息,它和后文介绍的IdleTimerCmd有关,主要用来监视网络设备的收发工作状态。当对应设备工作或空闲时间超过设置的监控时间后,Kernel将会发送携带其状态(idle或active)的UEvent消息。

图2-3所示为NetlinkHandler的工作流程。图2-3 NM工作流程

由图2-3可知,NM创建NetlinkHandler后,工作便转交给NetlinkHandler来完成,而每个NetlinkHandler对象均会单独创建一个线程用于接收socket消息。当Kernel发送UEvent消息后,NetlinkHandler便从select调用中返回,然后调用其onDataAvailable函数,该函数内部会创建一个NetlinkEvent对象。NetlinkEvent对象根据socket创建时指定的解析类型去解析来自Kernel的UEvent消息。最终NetlinkHandler的onEvent将被调用,不同的UEvent消息将在此函数中进行分类处理。NetlinkHandler最终将处理结果经由NM内部变量mBroadcaster转发给NetworkManagementService。

提醒 请读者结合上文所述流程自行研读相关代码。

2.2.3 CommandListener分析

Netd中第二个重要成员是CommandListener(以后简称CL),其主要作用是接收来自Framework层NetworkManageService的命令。从角色来看,CL仅是一个Listener。它在收到命令后,只是将它们转交给对应的命令处理对象去处理。CL内部定义了许多命令,而这些命令都有较深的背景知识。本节以分析CL的工作流程为主,而相关的命令处理则放到后文分析。

图2-4所示为CL中的Command对象及对应的Controller对象。图2-4 CL中的命令及控制类

由图2-4可知,CL定义了11个和网络相关的Command类。这些类均从NetdCommand派生(注意,为保持绘图简洁,这11个Command的派生关系由1个派生箭头表达)。CL还定义了10个控制类,这些控制类将和命令类共同完成相应的命令处理工作。

结合图2-2中对NM家族成员的介绍,CL创建时,需要注册自己支持的命令类。这部分代码在其构造函数中实现,代码如下所示。

[-->CommandListener::CommandListener构造函数]CommandListener::CommandListener() : FrameworkListener("netd", true) { registerCmd(new InterfaceCmd()); // 注册11个命令类对象 registerCmd(new IpFwdCmd()); registerCmd(new TetherCmd()); registerCmd(new NatCmd()); registerCmd(new ListTtysCmd()); registerCmd(new PppdCmd()); registerCmd(new SoftapCmd()); registerCmd(new BandwidthControlCmd()); registerCmd(new IdletimerControlCmd()); registerCmd(new ResolverCmd()); registerCmd(new FirewallCmd()); // 创建对应的控制类对象 if (!sSecondaryTableCtrl) sSecondaryTableCtrl = new SecondaryTableController(); if (!sTetherCtrl) sTetherCtrl = new TetherController(); if (!sNatCtrl) sNatCtrl = new NatController(sSecondaryTableCtrl); if (!sPppCtrl) sPppCtrl = new PppController(); if (!sSoftapCtrl) sSoftapCtrl = new SoftapController(); if (!sBandwidthCtrl) sBandwidthCtrl = new BandwidthController(); if (!sIdletimerCtrl) sIdletimerCtrl = new IdletimerController(); if (!sResolverCtrl) sResolverCtrl = new ResolverController(); if (!sFirewallCtrl) sFirewallCtrl = new FirewallController(); if (!sInterfaceCtrl) sInterfaceCtrl = new InterfaceController(); // 其他重要工作,后文再分析}

由于CL的间接基类也是SocketListener,所以其工作流程和NetlinkHandler类似。图2-5给出了CL的工作流程。图2-5 CL的工作流程

图2-5中,假设Client端发送的命令名是"nat",当CL收到这个命令后,首先会从其构造函数中注册的那些命令对象中找到对应该名字(即nat)的命令对象,其结果就是图中的NatCmd对象。而该命令最终的处理工作将由此NatCmd对象的runCommand函数完成。

2.2.4 DnsProxyListener分析

DnsProxyListener和Android系统中的DNS管理有关。什么是DNS呢?Android系统中DNS又有什么特点呢?来看下文。[3]

1.Android DNS简介

DNS(Domain Name System,域名系统)主要作用是在域名和IP地址之间建立一种映射。简单来说,DNS的功能类似于电话簿,它可将人名映射到相应的电话号码。在DNS中,人名就是域名,电话号码就是IP地址。域名系统的管理由DNS服务器来完成。全球范围内的DNS服务器共同构成了一个分布式的域名-IP数据库。

对使用域名来发起网络操作的网络程序来说,其域名解析工作主要分两步。

1)将域名转换成IP。由于域名和IP的转换关系存储在DNS服务器上,所以该网络程序要向DNS服务器发起请求,以获取域名对应的IP地址。

2)DNS服务器根据DNS解析规则解析并得到该域名对应的IP地址,然后返回给客户端。在DNS中,每一个域名和IP的对应关系称为一条记录。客户端一般会缓存这条记录以备后续之用。

提醒 DNS解析规则比较复杂,感兴趣的读者可研究DNS的相关协议。

对软件开发者来说,常用的域名解析socket API有两个。

·getaddrinfo:根据指定的host名或service名得到对应的IP地址(由结构体addrinfo表达)。

·getnameinfo:根据指定的IP地址(由结构体sockaddr表达)得到对应的host或service的名称。

Android中,这两个函数均由Bionic C实现。其代码实现基于NetBSD的解析库(resolver library),并经过一些修改。这些修改如下。

·没有实现name-server-switch功能。这是为了保持Bionic C库的轻便性而做的裁剪。

·DNS服务器的配置文件由/etc/resolv.conf变成/system/etc/resolv.conf①。在Android系统中,/etc目录实际上为/system/etc目录的链接。resolv.conf存储的是DNS服务器的IP地址。

·系统属性中保存了一些DNS服务器的地址,它们通过诸如"net.dns1"或"net.dns2"之类的属性来表达。这些属性由dhcpd进程或其他系统模块负责维护。

·每个进程还可以设置进程特定的DNS服务器地址,它们通过诸如"net.dns1."或"net.dns2."的系统属性来表达。

·不同的网络设备也有对应的DNS服务器地址,例如通过wlan接口发起的网络操作,其对应的DNS服务器由系统属性"net.wlan.dns1"表示。

图2-6所示为三星Galaxy Note 2中有关dns的信息。由图可知,系统中有些进程有自己特定的DNS服务器。不同网络设备也设置了对应的DNS服务器地址。图2-6 net.dns设置

2.getaddrinfo函数分析

本节介绍Android中getaddrinfo的实现,我们将只关注Android对其做的改动。

[-->getaddrinfo.c::getaddrinfo]int getaddrinfo(const char *hostname, const char *servname, const struct addrinfo *hints, struct addrinfo **res){ ......// getaddrinfo的正常处理 // Android平台的特殊定制 if (android_getaddrinfo_proxy(hostname, servname, hints, res) == 0) { return 0; } ......// 如果上述函数处理失败,则继续getaddrinfo的正常处理 return error}

由上述代码可知,Android平台中的getaddrinfo会调用其定制的android_getaddrinfo_proxy函数完成一些特殊操作,该函数的实现如下所示。

[-->getaddrinfo.c::android_getaddrinfo_proxy]static int android_getaddrinfo_proxy(const char *hostname, const char *servname, const struct addrinfo *hints, struct addrinfo **res){ ...... // 取ANDROID_DNS_MODE环境变量。只有Netd进程设置了它 const char* cache_mode = getenv("ANDROID_DNS_MODE"); ...... // 由于Netd进程设置了此环境变量,故Netd进程调用getaddrinfo将不会采用这套定制的方法 if (cache_mode != NULL && strcmp(cache_mode, "local") == 0) { return -1; } // 获取本进程对应的DNS地址 snprintf(propname, sizeof(propname), "net.dns1.%d", getpid()); if (__system_property_get(propname, propvalue) > 0) { return -1; } // 建立和Netd中DnsProxyListener的连接,将请求转发给它去执行 sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { return -1; } ...... strlcpy(proxy_addr.sun_path, "/dev/socket/dnsproxyd", sizeof(proxy_addr.sun_path)); ......// 发送请求,处理回复等 return -1;}

由上述代码可知:

·当Netd进程调用getaddrinfo时,由于其设置了ANDROID_DNS_MODE环境变量,所以该函数会继续原来的流程。

·当非Netd进程调用getaddrinfo函数时,首先会开展android_getaddrinfo_proxy中的工作,即判断该进程是否有定制的DNS服务器,如果没有它将和位于Netd进程中的dnsproxyd监听socket建立连接,然后把请求发给DnsProxyListener去执行。

3.DnsProxyListener命令

下面介绍DnsProxyListener(以后简称DPL),图2-7所示为其家族成员示意图。

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载