push推送浅析

from;

http://no001.blog.51cto.com/1142339/687076

APP 开发基础设置

1.在iPhone Provisioning Portal中建立好APP ID和Device.
2. 在Keychain Access.app中生成证书请求CertificateSigningRequest.certSigningRequest(菜单 > Keychain Access > Certificate Assistant > Request a Certificate From a Certificate Authority...).
3.在iPhone Provisioning Portal > Certificates中请求一个证书(点击Request Certificate,上传CertificateSigningRequest.certSigningRequest).
4.请求完成后,将证书文件(developer_identity.cer)下载,双击导入到Key Chain中.
5.在iPhone Provisioning Portal > Provisioning 中,新建一个Profile, 选择指定的APP ID和 Devices后生成.
6.将刚刚生成的Profile下载为*_profile.mobileprovision, 双击该文件, 将profile加载到iPhone中.

Push Notification service设置
7.在iPhone Provisioning Portal > App IDs,选择需要Push服务的App ID, 进入Configure.
8.确认 Enable for Apple Push Notification service ,配置 Development Push SSL Certificate, 上传第2步生成的证书请求.
9.下载生成的aps_developer_identity.cer, 完成Push服务配置.
10.双击aps_developer_identity.cer,保存到Key Chain.

生成php Push Notification sender需要的证书文件
11.在Keychain Access.app里选定这个新证书(Apple Development Push Services*),导出到桌面,保存为Certificates.p12.
12.运行如下命令:

   1.     openssl pkcs12 -clcerts -nokeys -out cert.pem -in Certificates.p12
   2.     openssl pkcs12 -nocerts -out key.pem -in Certificates.p12
   3.     openssl rsa -in key.pem -out key.unencrypted.pem
   4.     cat cert.pem key.unencrypted.pem > ck.pem

获得php Push Notification sender所需的设备令牌:

13.新建一个View-based Application项目,在$PROJECT_NAMEAppDelegate.m中:

a.粘贴如下代码:

   1. - (void)applicationDidFinishLaunching:(UIApplication *)app {
   2.     // other setup tasks here….
   3.     [window addSubview:viewController.view];
   4.     [self alertNotice:@"" withMSG:@"Initiating Remote Noticationss Are Active" cancleButtonTitle:@"Ok" otherButtonTitle:@""];
   5.     [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge |UIRemoteNotificationTypeSound)];
   6. }
   7. - (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
   8.     //NSLog(@"devToken=%@",deviceToken);
   9.     [self alertNotice:@"" withMSG:[NSString stringWithFormat:@"devToken=%@",deviceToken] cancleButtonTitle:@"Ok" otherButtonTitle:@""];
10. }
11. - (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
12.     NSLog(@"Error in registration. Error: %@", err);
13.     [self alertNotice:@"" withMSG:[NSString stringWithFormat:@"Error in registration. Error: %@", err] cancleButtonTitle:@"Ok" otherButtonTitle:@""];
14. }
15. -(void)alertNotice:(NSString *)title withMSG:(NSString *)msg cancleButtonTitle:(NSString *)cancleTitle otherButtonTitle:(NSString *)otherTitle{
16.     UIAlertView *alert;
17.     if([otherTitle isEqualToString:@""])
18.         alert = [[UIAlertView alloc] initWithTitle:title message:msg delegate:self cancelButtonTitle:cancleTitle otherButtonTitles:nil,nil];
19.     else
20.         alert = [[UIAlertView alloc] initWithTitle:title message:msg delegate:self cancelButtonTitle:cancleTitle otherButtonTitles:otherTitle,nil];
21.     [alert show];
22.     [alert release];
23. }

b.在 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 方法中增加

   1.     [self alertNotice:@"" withMSG:@"Initiating Remote Noticationss Are Active" cancleButtonTitle:@"Ok" otherButtonTitle:@""];
   2.     [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge |UIRemoteNotificationTypeSound)];

14.项目设置
a.Targets > $APP_NAME > context menu > Properties > Identifier
    修改 identifier 为App ID

b.Targets > $APP_NAME > context menu > Build > Code Signing > Code Signing Identifier > Any iPhone OS Device
    指定 iPhone Developer 为开发用机

15.编译并运行后会在iPhone上显示设备令牌

16.php Push Notification sender代码如下:
   1. <?php
   2. $deviceToken = "设备令牌";
   3.  
   4. $body = array("aps" => array("alert" => 'message', "badge" => 1, "sound" => 'received5.caf'));
   5.  
   6. $ctx = stream_context_create();
   7. stream_context_set_option($ctx, "ssl", "local_cert", "ck.pem");
   8.  
   9. $fp = stream_socket_client("ssl://gateway.sandbox.push.apple.com:2195", $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
10. if (!$fp) {
11.     print "Failed to connect $err $errstrn";
12.     return;
13. }
14. print "Connection OK\n";
15. $payload = json_encode($body);
16. $msg = chr(0) . pack("n",32) . pack("H*", $deviceToken) . pack("n",strlen($payload)) . $payload;
17. print "sending message :" . $payload . "\n";
18. fwrite($fp, $msg);
19. fclose($fp);
20. ?>


通过以上的工作基本就可以发送push通知了:
打开终端,并且找到push的php文件,通过命令符:php ./php文件名.php 来进行运行,恭喜你应该可以收到通知了。如果没有收到可能是证书生成的有问题!请仔细检查一下!
千万不要以为这样就大功告成了,我曾在上传项目前犯了个很低级的错误:用测试证书发送正式的push地址(不加sandbox),于是怎么都收不到push消息,弄的我很甭溃。
下面浅谈一下push的基本原理:
 

第一部分:Push原理(以下绝大多数内容参考自、图片来自iPhone OS Reference Library)

机制简介Push的工作机制可以简单的概括为下图

图中,

 

Provider是指某个iPhone软件的Push服务器。

APNS是Apple Push Notification Service(Apple Push服务器)的缩写,下文统一使用该缩写。因此,整个过程可以分为三个阶段,下面用大家常用的聊天客户端BeejiveIM来说明。(BeejiveIM是一款支持多账户登录的支持Push的iPhone聊天客户端,支持MSN、Google Talk等)

此时Provider为BeejiveIM服务器,我们在BeejiveIM上登陆MSN,其实软件是先把登录信息发送到BeejiveIM服务器,再通过其服务器来登陆MSN。因此,当我关闭了BeejiveIM,BeejiveIM服务器会继续为我登陆MSN,此时如果有人对我的MSN账户发送了消息,那么就会触发Push。此时:

 

第一阶段:BeejiveIM服务器把要发送的消息、目的iPhone的标识打包,发给APNS。

第二阶段:APNS在自身的已注册Push服务的iPhone列表中,查找有相应标识的iPhone,并把消息发到iPhone。

第三阶段:iPhone把发来的消息传递给相应的应用程序,并且按照设定弹出Push通知。Push认证许多朋友说Push不能用。其中一大部分,就是在认证阶段就出了问题。想了解原因?请细看:

 

这里所说的认证机制,实际上包含两层。一层是物理连接上的认证,另一层则才是涉及到iPhone设备令牌的认证。

物理连接上的认证:SSL/TLS链接

(如果你了解TSL,那么这里我几乎无需介绍。)

iPhone在开启Push的时候,会连接APNS建立一条TLS加密链接。每一台正常的iPhone都有一个独有的设备证书,而APNS也有一个服务器证书。两者建立的时候,会验证彼此的证书有效性。

TLS链接一旦建立,在没有数据的情况下,只需要每隔15分钟进行一次保活的握手,因此几乎不占流量。而一旦因为意外原因导致链接中断,iPhone会不断重新尝试建立TLS链接,直到成功。

更高一层次:基于token(令牌)的认证在机制简介里,我提到过APNS判断Push推送消息该发给哪台iPhone的依据是一个“目的iPhone的标识”,这个标识就是device token(设备令牌)

设备令牌是怎么生成的呢?是每次建立TLS连接时,APNS通过前一层次(TLS层)里我们提到的每台正常的iPhone唯一的设备证书(unique device certificate),并用令牌密钥(token key)加密生成的。

 

在令牌生成了之后,APNS会把设备令牌(device token)返回给iPhone,而对应的Push应用程序(如BeejiveIM),则把返回来的设备令牌(device token)直接发送给Provider(如BeejiveIM服务器)。这样,当Provider有Push消息要发送时,就会把对应帐号的设备令牌(device token)和消息一起发送给APNS,而APNS再依据设备令牌(device token),找到相应TLS链接的iPhone,并发送相应的Push消息

 

图中,Client App是iPhone上的Push应用程序。(图中缺了一条(当有Push消息时)由Provider到APNS的链接)

最重要的部分——每台iPhone独有的设备证书和密钥的来历正常的iPhone刷系统之后,是没有设备证书和密钥的。这就是为什么iPhone会需要连接到iTunes上进行激活——激活过程中,Apple会分配给每台iPhone独一无二的设备证书(device certificate)和密钥(key)

以上我仅仅介绍了从iPhone到APNS的链接建立。其实从Provider到APNS也有一条TLS链接,但是与本文关系不大,所以不多加介绍了。

我的PUSH问题出在哪里?相信许多同学都抱有这样的疑问。

正如上文提到的,iPhone的Push需要APNS生成对应iPhone的设备令牌,但生成这个令牌又需要iPhone上的有效的设备证书(device certificate)和密钥(key),但是:

iPhone OS 3.X 使用blacksn0w进行解锁的过程,是不经过iTunes的,而blacksn0w本身又不生成对应的设备证书(device certificate)和密钥(key),因此这样解锁完的iPhone根本不可能与APNS建立任何的TLS链接,Push自然废了。

有关各种pushfix补丁要修补这个问题,唯一的办法就是重新生成唯一且有效的设备证书(device certificate)和密钥(key)

但是要知道,证书是需要机构签发的,自己一个人随便弄的一个证书,只会被APNS认为是无效证书。(SSL证书一个多少钱大家可以去查查)

并且对应的文件似乎还和iPhone本机的一些内容相关,不是直接制作好的文件放进去就可以的。

于是,最早,dev team推出了一个测试版补丁,Push fix by dev team(通过他们的twitter发布的,因此官网没有消息)。这个补丁初期很有效。但是后来发现使用的是同一个证书。并且仅在iPhone 2G上比较正常。

之后某人士发布pushfix 1.0了。由于使用了不同的生成方法,因此在新版本iPhone上也正常工作了。于是风靡一时。

然而,以上两个补丁都有严重的隐患——他们使用了一个固定的证书作为设备证书(device certificate)。因此在不同iPhone上的区别仅仅在于生成的密钥(key)不同。

上面提到过,APNS依靠每台iPhone独一无二的设备证书(device certificate)和密钥(key)来生成独一无二的设备令牌(device token),用来标识每台iPhone。

但当多个iPhone的设备证书(device certificate)完全一致时,就存在一定几率使得多个iPhone获得相同的设备令牌(device token)——也就意味着,这几台iPhone获得的Push通知会串掉,指不定被同步到哪台上去。

而随着这两个补丁的使用人数不断增加,使得出现获得相同设备令牌(device token)的iPhone数量大大增加了。于是杯具开始了……

之后,Pushfix的作者,声称自己可以为每台iPhone手工制作唯一的设备证书(device certificate)和密钥(key),并且开始提供了付费服务,并且最终推出了付费的Pushfix 2.0——其通过cydia安装的原理是,在安装的时候在线连接到pushfix站点检查对应iPhone的imei确定是否付费再自动下载对应的证书。

虽然不知道他是怎么制作这些证书的。但是经过晓晓的验证,他制作的证书确实是有效的。Push问题确实修复了。

在这之后,某论坛上出现了一个叫做Pushfix_D的补丁,声称无需付费也能直接修复问题。然而,

——暂时隐藏——

其他出错的情况我的iPhone在cmwap下无法push?!对的,这完全正常。在wap网内,TLS链接几乎无法建立成功。

我的iPhone在Wi-Fi下无法push?!实际上这得说是iPhone与某些无线路由器的不兼容。如果无线路由器开启了DNS转发功能,那么很有可能你的iPhone无法成功与APNS服务器建立TLS链接。

解决方法:

关闭无线路由器的DNS转发功能,手动为iPhone的Wi-Fi连接设置DNS为8.8.8.8

补充,实际上,这也就是为什么iPhone连接到Wi-Fi上而又不能收到Push的时候,会变得发热且非常耗电。因为iPhone会不断尝试建立TLS链接

如何得知我的Push是否破解成功?一个简单的方法就是安装 Twitbird Pro版本。在其Accounts页面,会显示当前软件的Push注册状况。

或者你可以用WinSCP之类的软件查看iPhone上的

/var/mobile/Library/Preferences/com.apple.apsd.plist

文件状态。

 

如果其大小为119字节,则说明该iPhone已经成功取得了设备令牌(device token),并保存在该文件中。

如果小于该大小,则说明该iPhone已经和APNS链接过,但是未能取得设备令牌(device token)。

如果没有该文件,那说明该iPhone根本没能成功连接到APNS。其他一些值得注意的问题

iPod Touch与iPhone的Push机制不完全相同,锁屏后15分钟方检查一次。故请勿与上文对号入座。

APNS在发送Push消息时,如果发现对应的iPhone链接中断,则会延后几分钟再发送。超过一个时间后,Push消息会被删除。因此请注意你的网络状况是否影响Push正常工作。

如上文所说,每台iPhone的设备令牌(device token)储存在/var/mobile/Library/Preferences/com.apple.apsd.plist 文件中。这就是为什么每次需要重装Push补丁时,建议删除push程序并删除该文件。

使用sbsettings的EDGE开关关闭EDGE,却不关闭Push的话,会导致iPhone不断尝试建立TLS连接,最终耗尽电量。因此,如果你不打算或不能用Push,请关闭Push选项。

对软件的Push服务器(Provider)而言,Wi-Fi与手机网络是一样的,在Push处理上不会有任何区别。

虽然已经解释的很清楚,但还是明说一句,只要TLS连接正常,Push服务就是实时的,速度仅取决于Provider而已。

题外话,iPhone上的邮件推送为Push Mail技术,与本文所说的Push完全不同。请查阅Exchange相关内容。福利一句话,如果你觉得Push没什么用,那只能说明你见识太少。在以下页面可以查看一些支持Push的优秀软件。

http://appadvice.com/applists/show/definitive-list-of-push-capable-apps


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值