(转)移动端主动推送消息原理

转:https://www.zhihu.com/question/19628406/answer/77205019

一、服务端主动推送消息到客户端过程

作者:谢泽帆   李琰
链接:https://www.zhihu.com/question/24938934/answer/85359794
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

服务端主动推送到客户端是怎么一个过程


目前服务端给客户端推送,普遍做法是客户端与服务端维持一个长连接,客户端定时向服务端发送心跳以维持这个长连接。当有新消息过来的时候,服务端查出该消息对应的TCP Channel的ID并找到对应的通道进行消息下发。


这只是最基本的通讯模型,在此之上,有衍生出针对消息的发布/订阅模型,客户端可以订阅某一个Topic,服务端根据Topic找到对应的Channel进行批量的消息下发。所有的客户端隐式的订阅的all这个opic,所以『类似中国移动给全网信号内所有手机发消息的模式』亦可以理解『广播消息』,即给all这个Topic发消息。


在此基础上,又要几个开源的协议来帮你定义这个事情,比较有名的如MQTT协议(刚好这几天我看到MQTT协议的中文翻译,分享给大家),Github上搜索MQTT可以找到对应的开源的协议实现项目,有兴趣可以自行搜索。


现在的APP是如何使用消息推送的


实际上,主流的移动平台都已经有系统级的推送产品,Android上有GCM,iOS上有APNS,WinPhone有MPNS。但因为某些你懂的原因,GCM在国内处于不可用状态,所以国内的移动应用采用另外一种做法---在后台运行一个Service,维持应用于服务端的TCP长连接,以达到实时消息送达的效果。


但是在移动端如何稳定的维持长连接是一件非常复杂的事情,前面说了,客户端通过定时发送心跳信号(Heartbeat)以维持与服务端的长连接,但是,如果心跳的频率太频繁,移动设备耗电增加,心跳间隔太久又可能使得连接被断开。并且普遍认为移动设备处于一个多变的网络环境中,WIFI,2G,4G切换,基站切换都会引起网络变动,在不同网络环境下的心跳频率,与网络变动的重连动作,都需要大量的数据统计分析总结出来。


这仅仅是客户端的难题,在如今移动应用动辄成百上千的用户量的情况下,如何维护如此多的长连接,如果应对大规模的消息下发以及后续针对下发消息的各种统计动作都是技术难点。


再者,现在应用一般都是全平台的,发送一条消息,应该同时发送给Android,iOS, WinPhone,Android端走自建的TCP长连接通道,iOS与WinPhone走自家的系统推送通道。那么意味着你服务端要维护这三套推送系统。


显然对于小团队,要独自建立一套消息推送系统的难度非常大,所以市场上涌现出很多优秀的推送产品,帮开发者聚合这些推送方式,并提供统一的推送接口。国外如 Urban Airship, Parse等, 国内有JPush百度云推送信鸽LeanCloud等。(比较遗憾的是,非常优秀的Parse已经被Facebook宣布停止开发,并将于1年后关闭)


现在除了体量非常大的公司自建推送系统外,一般普通公司都是使用第三方推送服务,以上所有的第三方推送服务,基础功能都是免费的,如果有条件的话,建议可以集成多家服务,A/B测试对比下推送效果,本人从事与以上某推送公司,在此就不评价各家产品好坏了。

二、服务器端主动推送消息到客户端原理

现在手机主流的几个平台都有自家提供Push的功能,让应用开发者能够很方便地把Push能力集成到应用中。

Android 上有 GCM (Google Cloud Messaging)
iOS 上有 APNs(Apple Push Notification service)
Windows Phone 上有 MPNs(Microsoft Push Notification service)。

但是由于Windows Phone的市场占比不高,所以一般也就没有人会专门做wp系统的推送。至于Android的GCM在国内基本上是不可用的。原因主要有以下两点:
一、国内大部分Android手机都不带Google服务,也就用不了GCM,这是主要的问题。
二、在国内Google的服务一般都不太稳定,原因你懂的。
所以现在在做消息推送的时候Android平台采用服务器与设备直接拉一条长连接的方式实现功能,而IOS平台则采用苹果自己的APNs服务。在分别说这两个平台推送原理的时候,先回答一下题主关于服务器如何先找到设备、再找到app的问题。每一个设备都有一个自己的设备号,而设备中的app又都有一个唯一的包名。所以服务器只需要找到设备号与包名就可以定位到某个设备的某个应用,而这设备号与包名会一起构成一个标识符,叫做device_token,因此问题就简化为把device_token与消息内容等信息交给服务器,服务器把内容发到唯一的device_token上。这就好像你在上海要通过顺丰寄送一个快件儿给某某小区的某某房间,那么快件儿首先会邮递到顺丰公司在北京的总站点,之后再根据小区的地址投递/路由到某某房间,这样一个寄件过程就算完成了。在这里,你要寄送的快件儿就是你要发的“消息”,送达房间相当于最终“接收消息的App”,顺丰公司在北京的总站点相当于这里提到的“设备”,送达房间的房间号就相当于这个环节里面提到的“包名”。

接下来分别简单说一下这两个平台的推送实现原理。
首先是IOS平台,IOS的推送是通过苹果自己的APNs服务进行的,用户需要将device_token以及消息内容等推送信息交给APNs服务器,剩下的均由苹果自己来完成。但是如果提供的device_token是失效的(app被卸载、系统版本升级导致device_token变化等情况)那么推送过程就会被中断,频繁的断线重连甚至会被APNs认为是一直DoS攻击。详情可以参考为什么苹果的推送,两次推送之间间隔比较久的话,第二次推送会很慢? - 沙漠的回答

Android平台推送原理:

Android平台在不使用GCM的情况下就需要将自己的服务器或是第三方推送服务提供商的服务器与设备建立一条长连接,通过长连接进行推送。但是不建议自己设置服务器实现推送功能,一是因为成本太高(开发成本、维护成本),自己搭建的服务器无论是稳定性还是速度上都比不了第三方推送服务提供商的效果。另一个是因为自己的数据量较小,使用第三方推送服务提供商可以用他们的维度进行推送,实现精准推送。友盟推送就是做的比较好的,可以根据用户分群、地区、语言等多维度进行推送,最大程度减少对于用户的干扰,仅把消息推送给相关用户。友盟推送的优势是什么? - 李琰的回答
下图是Android平台消息推送的简单示意图。

开发者通过第三方推送服务提供商将信息直接下发给需要的设备,第三方推送服务提供商与设备建立一条长连接通道,并且将消息路由到APP中(图中的设备1与设备2),对于像设备3这种无网络连接或是没有成功建立长连接通道的设备,会在设备3连网且推送消息没有过期的情况下自动收到由第三方推送服务提供商推送过来的消息,保证消息不会丢失。

iOS的push推送原理:

iOS应用的推送大部分情况下都要依赖苹果生态提供的APNs(Apple Push Notification Service)服务。
下边用两幅图来简要说明其推送原理

 


首先作为设备标识的device-token是由APNs颁发的,App开发者或者第三方推送平台(图中的Provider)做的工作是收集这个device-token,APNs的推送是要求基于APNs颁发的device-token来推送的。只有正确的device-token会被APNs接受,如果是一个错误的、或者无效的device-token(比如App已经卸载了),APNs就不会接受。

接着开发者使用第三方推送平台(图中的Provider)在将推送内容与范围选定之后进行推送,第三方推送平台将信息提交给APNs,剩下的操作全部都由APNs来进行完成,整个过程第三方推送平台就不能控制了。

对于友盟推送有更多想要了解的东西可以关注友盟推送的官网友盟消息推送|app推送以及论坛

在开发企业app的时候,有的时候基于安全性的考虑,不允许设备连接外网。这就出现了一个问题,就是iOS的推送功能没法工作了,因为iOS的推送功能是固化在系统里,必须连接苹果的APNS服务器才能工作的,为了能让这类只能工作在内网里的app也能拥有推送功能,就需要我们自己来实现推送功能了。

自主推送的实现方法是利用iOS的voip类app可以驻留在后台的功能。这类app,系统在设备开机时即被启动,app可以将自己的一个socket委托给系统,在socket有数据到达时,系统就会唤醒app,给它一段很短的cpu时间来处理数据,再加上UIApplication的keepAliveTimeout handler(最小10分钟间隔一次),可以每隔一段时间就重新建立一次连接,来达到保持socket长连接的需求。

注:由于voip类app可以使app常驻在后台并维持socket长连接,因此苹果对这类应用有及其严格的审查,所有不是真正的voip的app都会被拒绝!切记!

1.设置应用为voip应用

打开<app>-info.plist文件,加入如下的key

Required background modes,追加App provides Voice over IP services

 

2.将socket设置成异步模式,并将socket设置成VOIP类的,以便系统能够托管它。

CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)(mosq->sock), &readStream, NULL);
//保证不关闭原来的socket
CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
//设置成voip socket
CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);

 

 

3.安装keepAliveTimeout handler,让系统每隔一段时间重新建立连接

    UIApplication *application = [UIApplication sharedApplication];
    [application setKeepAliveTimeout:600 handler:^{
        [self reconnect];
    }];

 

我参考了jmsnil的MQTTExample工程,这是一个使用mqtt的例子,我把它做了一些修改,以便能在后台运行接收推送。

原工程地址:

https://github.com/jmesnil/MQTTExample

我修改的示例工程地址:

https://github.com/Guou/Demo-mqtt-push

打开终端,输入下面的命令即可获得通知

curl -X PUT --data-binary "1"  http://eclipse.ttbridge.com/%2FMQTTExample%2Ftestcnpush

 

该例子只可以工作在真机上,如果您是iOS7以下,请把application:didFinishLaunchingWithOptions:中本地通知权限请求的代码删掉。

--------------------

我感觉全部内网环境做推送消息的想法还是算了吧,太麻烦,直接走短信和邮件通知得了

posted @ 2017-07-27 16:18  wangle100  阅读(5417)  评论(0编辑  收藏  举报