开篇废话
首选声明我是一个嵌入式系统开发工程师,我整理这篇文章的目的,其实是希望通过借鉴手机连接GPRS的流程,来完善系统内部对于GSM模块集成的稳定性。所有内容来自网络,在文末的参考链接标注了出处,如有冒犯原作者的地方请指正。
名词解释
CMNET/CMWAP:CMWAP 和CMNET 只是移动人为划分的两个GPRS接入方式。前者是为手机WAP上网而设立的,后者则主要是为PC、 笔记本电脑、PDA等利用GPRS上网服务。它们在实现方式上并没有任何差别,但因为定位不同,所以和CMNET相比,CMWAP便有了部分限制,资费上也存在差别。CMWAP的端口为80和8080(HTTP代理),9201(WAP网关协议)。
APN:Access PointName,接入点名称。是您在通过手机上网时必须配置的一个参数,它决定了您的手机通过哪种接入方式来访问网络。
PDP:PacketDataProtocol分组数据协议。是GPRS连接的软硬件环境,指定GPRS连接的接入点APN,连接类型IP或PPP,还有其他一些可选项。
PPP:Point-to-PointProtocol 业务流模板。终端和MODEM之间点对点的协议,包括终端于MODEM之间的链路层协商(LCP),服务器对终端的认证(PAP或CHAP,这一步非强制),以及终端与服务器的网络层协商(基本都是IPCP)。PPP相当于链路层协议socket套接字,对TCP/IP协议的封装、应用。GPRS上网首先要设置PDP,接着建立PPP连接,PPP连接建立之后,就可以进行TCP/IP传输了,要进行TCP/IP数据传输,很多时候都采用socket。
SOCKET:进程之间的通信方式,手机上的应用程序(客户进程)要和服务器的某个服务进程通信,就用socket通过邦定的TCP或UDP端口基于IP进行数据传输。
RIL:Radio Interface Layer 无线接口层。RIL工作在PPP、TCP/IP协议之下,负责数据的可靠传输、AT命令的发送以及response的解析。除了对网络的支持,RIL也支持SMS、Voice Call等功能。
Data Connect流程分析
Android的数据连接是基于PPP方式的,主要步骤为:首先通过AT命令激活PDP连接,然后利用pppd通过数据端口完成拨号连接。数据连接的核心控制类是DataConnectionTracker,存在于GSMPhone里,数据连接不需要用户的干预,在APN设置好之后,在适当的情况下就会自动激活,激活的入口点是:DataConnectionTracker.trySetupData→setupData→PdpConnection.connetc→CommandsInterface.setupDefaultPDP,通过PdpConnection访问GSMPhone中的RIL层的setupDefaultPDP实现,setupDefaultPDP的结果由EVENT_SETUP_PDP_DONE返回,如果成功,则开始调用pppd完成实际连接,这是通过DataLink.connect实现的;DataLink只是抽象基类,此处它的实现类是PppLink,实现DataLinkInterface接口,所以DataLink.connect实际上调用PppLink.connect,它通过SystemService.start(SERVICE_PPPD_GPRS)开始pppd服务,并通过checkPPP函数访问Linux的sys文件系统来查询pppd的连接状态,如果成功,便可以将LINKUP的消息通知出去以完成连接流程。
RILD源码分析
RIL对消息的处理是将消息通过LocalSocket发送到以rild为名称的有名端口。这个有名Socket的创建在ril.cpp代码中。s_fdListen=android_get_control_socket(SOCKET_NAME_RIL)
RILD是守护进程,执行的过程为:获取参数→打开功能库→建立事件循环→执行RIL_Init→RIL_register;事件循环式核心,通过Select多路复用机制,读取来自上层的Socket接口的具体操作命令,同时一些命令Timeout唤醒机制,也通过Select实现;
1. Request流程
命令下发流程:首先从JAVA层通过Socket将命令发送到RIL层的RILD守护进程,RILD中负责监听的ril_event_loop消息循环中的Select发现RILD Socket有了请求连接信号,建立一个record_stream,打通与上层的数据通道并开始接收请求数据,数据通道的回调函数processCommandsCallback()会保证收到一个完整的Request后,将其送达processCommandBuffer()函数;
解析过程:processCommandBuffer()从Socket中序列化的数据流里还原信息,将其组织到RequestInfo中;RequestInfo数据结构如下(存在于ril.cpp中):
typedef struct RequestInfo {
int32_t token;//this is not RIL_Token
CommandInfo *pCI;
struct RequestInfo *p_next;
char cancelled;
char local; // responses to local commands do not go back to command process
}RequestInfo;
RIL层以Request号为基础采用表驱动方式分发请求,CommandInfo结构表示命令的信息,关联了Request号和实际的请求函数,以及响应函数之间的关系;分发流程:s_callback.onRequest()完成分发操作,s_callback获取自libreference-ril的RIL_RadioFunction结构指针,Request请求在这里转入底层的libreference-ril处理,handler是reference-ril.cpp中的Request。
onRequest根据Request号进行简单的switch分发,然后将命令和参数转换成对应的AT命令,由writeline()完成驱动层的发送,writeline通过驱动程序节点的文件描述符进行写操作实现控制。
2.Response流程
Response有两类:unsolicited表示主动上报的消息,如来电,来短信等,而solicited是AT命令的响应,判断是否是solicited的依据有两点:一是当前用AT命令正在等待响应;二是读取的响应符合该AT命令的响应格式。对于Response流程来讲,流程是从Modem设备发回响应数据开始的。RIL通过readerLoop函数,利用readline逐行读取响应数据,随后通过processLine进行分析,主动上报的一般以+XXXX的形式出现,而AT命令的响应格式则有一行或多行之分,但最终一定以OK或者ERROR结尾,于是PrcessLine有以下几种情况:
1)、没有AT命令等待响应或不符合AT响应格式,一般是主动上报行,由handleUnsolicited处理,handleUnsolicited→onUnsolicetd→RIL_onUnsolicitedResponse;
2)、isFinalResponseSucess/isFinalResponseError是最终响应行,转到handleFinalResponse处理,handleFInalResponse会发送线程同步信号,激活等到的发送线程;
3)、符合当前AT命令响应格式的行,解析并获取数据,这是响应处理的中间过程,然后继续收到最终响应行,然后进入2流程最后的发送动作由sendResponse→sendResponseRaw→blockingWrite通过Socket回传给上层来完成,响应解析由上层完成。
专有APN
专有APN在功能上可以和Internet的VPN做类比,实际上他就是基于GPRS的VPN网络。
专有APN常见组网方式
1,运营商部署一条专线接入到企业的网络中,局端和企业端路由器之间采用私有IP进行连接。
2,局端互连路由器与GGSN采用GRE隧道连接。
专有APN的几个重要特点:
1,除非运营商分配一个Internet IP地址,否则计算机没有任何办法通过Internet访问该APN中的主机。
2,只有手机卡号在APN中的白名单之列,该手机才可以接入该APN。
3,企业客户可以建立一套RADIUS和DHCP服务器,GGSN向RADIUS服务器提供用户主叫号码,采用主叫号码和用户账号相结合的认证方式;用户通过认证后由DHCP服务器分配企业内部的静态IP地址。补充:该认证方式不一定适合于每个省的运营商,这取决于该省运营商的APN管理平台。
PPP和PDP激活
PPP协商过程中的IPCP配置中,终端通过MODEM请求激活PDP上下文获得IP地址完成网络连接,PDP中设置的APN就是终端所在的这个网络的网关,终端访问internet时就得通过这个网关; 而终端的客户进程与服务器的服务进程进行socket通信时,就基于这个IP地址。
据我所知道的:从网络侧来看,PPP连接最重要的一步是获取IP地址,这个IP由GGSN分配,GGSN是GPRS网到internet的网关,GSM和WCDMA协议规定一个MODEM可以和多个GGSN建立PDP上下文,不知道你所说的服务器是否指GGSN。而socket连接的服务器和GGSN完全是两码事,socket连接的是internet网络中的服务器,socket是用于进程间通信的,它将进程与TCP/UDP端口进行绑顶,一个client端的socket只能连接一个serversocket。也就决定了它只能连接一台服务器。如果你要写应用程序,你只需要关心socket的函数族就可以了,不需要去考虑GPRS如何如何,它对你是透明的。
当然,写应用的时候必须注意两件事:
1、GPRS是否可用。如果当前GPRS根本就无法连接,你开socket是毫无意义的;
2、拥塞控制。GPRS速度其实并不高,从理论上来说,如果你的平台上的TCP/IP足够完整,你根本不需要考虑GPRS的内容,只需要根据TCP/IP协议栈上的接口就完全可以控制应用程序的调度和流控了。通常的平台上,TCP/IP协议栈都是“赛扬”版的,所以应用程序经常还需要去读取GPRS状态。如果你写的是GPRS协议栈,关心到PDP就可以为止了。如果手机的GPRS没有长时间掉线,就不会变化。PDP就是处理这件事的。PDP的作用相当于维持一根看不见的网线,不管你走到哪儿都保证你IP地址不变。
PPP是发生在PDP和TCP/IP之间的。这里引入PPP基于两个历史渊源:
1、TCP/IP当然可以覆盖在PDP之上,但这几乎就是重写TCP/IP了。而更早的时候已经有以PPP为底层的TCP/IP了。PPP的移植又比TCP/IP的移植要简单得多。于是就有人先把PPP移植到PDP上,然后再在上面盖一层TCP/IP。PPP其实就是一个DL(数据链路层)的变异体;
2、(实际上这个才是真正的最主要的渊源),最开始的处理器能力都不够,跑GPRS之后,根本不可能再跑TCP/IP协议栈和应用,GPRS充当modem,TCP/IP和应用程序在PC一端。
GPRS的数据怎么弄进PC呢?这就是PPP的用武之地了。PC和GPRS modem用串口进行物理链接,串口之上覆盖PPP协议,PPP的一端是GPRS modem的PDP。另一端就是TCP/IP的数据链路层接口。