APNS苹果远程推送

1,852 阅读9分钟

什么是APNS?

苹果推送通知服务(APNs)是推送通知的网关,iPhone ipad 对于应用程序在后台运行有诸多限制,考虑到手机电池电量,应用不允许在后台进行过多的操作。因此,当用户切换到其他程序后,原先的程序无法保持运行状态。对于那些需要保持持续连接状态的应用程序(比如社区网络应用),将不能收到实时的信息。推送是解决轮询所造成的流量消耗和电量消耗的一个比较好的解决方案

为解决这一限制,苹果推出了APNs(苹果推送通知服务 Apple Push Notification services)。APNs 允许设备与苹果的推送通知服务器保持常连接状态。当你想发送一个推送通知给某个用户的iPhone上的应用程序时,你可以使用 APNs 发送一个推送消息给目标设备上已安装的某个应用程序。

苹果的推送服务APNs基本原理简单来说就是苹果利用自己专门的推送服务器(APNs)接收来自我们自己应用服务器的需要被推送的信息,然后推送到指定的iOS设备上,然后由设备通知到我们的应用程序,设备以通知或者声音的形式通知用户有新的消息。推送的前提是装有我们应用的设备需要向APNs服务器注册,注册成功后APNs服务器会返给我们一个device_token,拿到这个token后我们将这个token发给我们自己的应用服务器,当有需要被推送的消息时,我们的应用服务器会将消息按指定的格式打包,然后结合设备的device_token一并发给APNs服务器,由于我们的应用和APNs维持一个基于TCP的长连接,APNs将新消息推送到我们设备上,然后在屏幕上显示出新消息来。

推送流程

  • 获取设备device_token阶段

上图完成了如下步骤:

1.Device连接APNs服务器并携带设备序列号 2.连接成功,APNs经过打包和处理产生device_token并返回给注册的Device 3.Device携带获取的device_token向我们自己的应用服务器注册 4.完成需要被推送的Device在APNs服务器和我们自己的应用服务器注册

执行顺序如下所示:

这里要提到的一点是,我们的设备和APNS服务器之间的通讯是基于SSL协议的TCP流通讯,二者之间维持一个长连接,当从APNS服务器注册成功后,一定要将device_token发送给我们的应用服务器,因为在推送过程中,首相是由我们的应用服务器(上图中Provider)将需要推送的消息结合device_token按指定格式(后面会提到)打包然后发送给APNS服务器,然后由APNS服务器推送给我们的设备。

消息推送过程

推送的过程经过如下步骤:

19175942_bHsb.jpeg

1.首先,安装了具有推送功能的应用,我们的设备在有网络的情况下会连接苹果推送服务器,连接过程中,APNS会验证device_token,连接成功后维持一个长连接;

2.Provider(我们自己的服务器)收到需要被推送的消息并结合被推送设备的device_token一起打包发送给APNS服务器;

3.APNS服务器将推送信息推送给指定device_token的设备;

4.设备收到推送消息后通知我们的应用程序并显示和提示用户(声音、弹出框)

3.3 完整流程介绍

比较直观的流程参照下图:

![](http://upload-images.jianshu.io/upload_images/5220087-da89da5c9b8b43fb.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

应用启用推送通知功能,需要用户确认;

应用收到设备识别ID(device token),相当于接收推送通知的地址;

应用将设备识别ID发送到你开发的服务器;

当有推送通知的需要时,你就可以通过你开发的服务组件发送信息到苹果的服务器上;

苹果推送通知服务将信息推送到用户的设备上。

上图显示了我们的应用服务器将消息推送到我们的App的完整路径,其实真正完成推送的是APNS服务器,我们自己的应用服务器只是将需要推送的消息告诉苹果服务器,至于如何维护消息队列或如何保证消息能被推送到指定的设备上,这些都由苹果APNS给我们做完了

Push机制类型

四种:徽章、提示框、声音和横幅,具体表现形式如下图

  • Push机制的4个组件
  • Provider
  • APNS
  • iPhone设备
  • Client App 其中APNS(Apple Push Notification Service)是由苹果提供的消息推送服务中心,所有的消息都经由这里转发给相应的设备

证书生成

![Uploading 19175943_bPmM_098454.jpeg . . .]

先概述下大致过程,然后下面会截图给出详细的步骤

在Mac上生成 Apple推送通知SSL许可证:

  1. 登录到 apple Developer Connection Portal 并点击 App IDs
  2. 创建一个不使用通配符的 App ID 。通配符 ID 不能用于推送通知服务。例如,我们的iPhone程序ID像这样:54im.com.PushChat
  3. 点击App ID旁的“Configure”,然后按下按钮生产 推送通知许可证。根据“向导”指导的步骤生成一个签名并上传,最后下载生成的许可证。
  4. 通过双击.cer文件将你的 aps_developer_identity.cer 引入Keychain中。
  5. 在Mac上启动 Keychain助手,然后在login keychain中选择 Certificates分类。你将看到一个可扩展选项“Apple Development Push Servicescom.54im.PushChat”
  6. 扩展此选项然后右击“Apple Development Push Services” > Export “Apple Development Push Services:com.54im.PushChat”。保存为 PushChat_cert.p12 文件。
  7. 扩展“Apple Development Push Services” 对“Private Key”做同样操作,保存为 PushChat_key.p12 文件。
  8. 需要通过终端命令将这些文件转换为PEM格式: openssl pkcs12 -clcerts -nokeys -out cert.pem -in PushChat_cert.p12
  9. 转换得到key的pem: openssl pkcs12 -nocerts -out key.pem -in PushChat_key.p12
  10. 如果你想要移除密码,要么在导出/转换时不要设定或者执行: openssl rsa -in key.pem -out key.unencrypted.pem
  11. 最后,你需要将键和许可文件合成为apns-dev.pem文件,此文件在连接到APNS时需要使用: cat apns-dev-cert.pem key.unencrypted.pem > ck.pem

5.2.2 配置详解

  1. 创建APPID

首先登陆我们的Apple Developer后台为将要使用推送服务的App新建一个App ID,如下图,点击新建后输入基本信息

APPID创建好后,我们点编辑刚刚生成好的APPID,生成下development证书,生产情况下用 Production证书

创建正式过程中,要求上传一张Certificate Signing Request 证书请求签名文件

2. 生成证书请求文件 输入证书信息

####3. 生成PUSH证书 还记得刚刚苹果开发者那里要上传的证书不,将生成好的这个.certSigningRequest证书上传上去,

下载aps_development.cer这个证书到mac上,如果是发布版的推送证书,就为aps_production.cer。然后双击该证书,将推送证书安装到我们的Mac机器上,安装成功后会看到如下界面(如果是发布版,则证书的Development部分显示的是Production)

需要为certificate和它之下的private key各自export出一个.p12文件。(会出现设置密码过程)

  1. 导出公钥
    19175956_NaCN.jpeg

导出私钥

19175957_UHfr.jpeg

  1. key转换

需要将上面的2个.p12文件转成.pem格式:

openssl pkcs12 -clcerts -nokeys -out cert.pem -in Push_Chat_cert.p12 openssl pkcs12 -nocerts -out key.pem -in Push_Chat_cert_key.p12

如果需要对key不进行加密 openssl rsa -in key.pem -out key.unencrypted.pem

然后就可以 合并两个.pem文件, 这个ck.pem就是服务端需要的证书了 cat cert.pem key.unencrypted.pem > apns-dev.pem

创建 Provisioning Profile

5.3 创建 provisioning profile

  1. key转换

需要将上面的2个.p12文件转成.pem格式:

openssl pkcs12 -clcerts -nokeys -out cert.pem -in Push_Chat_cert.p12 openssl pkcs12 -nocerts -out key.pem -in Push_Chat_cert_key.p12

如果需要对key不进行加密 openssl rsa -in key.pem -out key.unencrypted.pem

然后就可以 合并两个.pem文件, 这个ck.pem就是服务端需要的证书了 cat cert.pem key.unencrypted.pem > apns-dev.pem

创建 Provisioning Profile 5.3 创建 provisioning profile

![](http://upload-images.jianshu.io/upload_images/5220087-3f1d413cc7539c33.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

19180001_675W.jpeg

将该prifiles文件下载下来,名字是PushChat.mobileprovision,其实不用下载,xcode里面会根据你的项目id自动去拉对于的这个文件

客户端制作

#import "AppDelegate.h"
@implementation AppDelegate
/**
* This is what you need to add to your applicationDidFinishLaunching
*/
 - (void)applicationDidFinishLaunching:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
 [[UIApplication sharedApplication]

 registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];

// Clear application badge when app launches
application.applicationIconBadgeNumber = 0;
}
- (void)applicationDidFinishLaunching:(UIApplication *)application
 {
// Add registration for remote notifications

[[UIApplication sharedApplication]

 registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
 // Clear application badge when app launches
application.applicationIconBadgeNumber = 0;
}
/*
* -------------------------------------------------------
*  BEGIN APNS CODE
*/
/**
* Fetch and Format Device Token and Register Important Information to Remote Server
*/
 - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
#if !TARGET_IPHONE_SIMULATOR
// Get Bundle Info for Remote Registration (handy if you have more than one app)

NSString *appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];
NSString *appVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
// Check what Notifications the user has turned on.  We registered for all three, but they may have manually disabled some or all of them.

NSUInteger rntypes = [[UIApplication  sharedApplication] enabledRemoteNotificationTypes];
// Set the defaults to disabled unless we find otherwise...
NSString *pushBadge = (rntypes & UIRemoteNotificationTypeBadge) ? @"enabled" : @"disabled";

NSString *pushAlert = (rntypes & UIRemoteNotificationTypeAlert) ? @"enabled" : @"disabled";

NSString *pushSound = (rntypes & UIRemoteNotificationTypeSound) ? @"enabled" : @"disabled";
// Get the users Device Model, Display Name, Unique ID, Token & Version Number
UIDevice *dev = [UIDevice currentDevice];
NSString *deviceName = dev.name;
NSString *deviceModel = dev.model;
NSString *deviceSystemVersion = dev.systemVersion;
// Prepare the Device Token for Registration (remove spaces and < >)
NSString *deviceToken = [[[[devToken description]                              stringByReplacingOccurrencesOfString:@"<"withString:@""]                          stringByReplacingOccurrencesOfString:@">" withString:@""]                          stringByReplacingOccurrencesOfString: @" " withString: @""];
// Build URL String for Registration

// !!! CHANGE "www.mywebsite.com" TO YOUR WEBSITE. Leave out the http://

// !!! SAMPLE: "secure.awesomeapp.com"
NSString *host = @"121.199.25.24/pushchat";
// !!! CHANGE "/apns.php?" TO THE PATH TO WHERE apns.php IS INSTALLED
// !!! ( MUST START WITH / AND END WITH ? ).
// !!! SAMPLE: "/path/to/apns.php?"
NSString *urlString = [NSString stringWithFormat:@"/apns.php?task=%@&appname=%@&appversion=%@&devicetoken=%@&devicename=%@&devicemodel=%@&deviceversion=%@&pushbadge=%@&pushalert=%@&pushsound=%@", @"register", appName,appVersion, deviceToken, deviceName, deviceModel, deviceSystemVersion, pushBadge, pushAlert, pushSound];

// Register the Device Data

// !!! CHANGE "http" TO "https" IF YOU ARE USING HTTPS PROTOCOL

NSURL *url = [[NSURL alloc] initWithScheme:@"http" host:host path:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];

NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];

[NSURLConnection sendAsynchronousRequest:request

                                   queue:[NSOperationQueue mainQueue]

                       completionHandler:^(NSURLResponse *urlR, NSData *returnData, NSError *e) {

                           NSLog(@"Return Data: %@", returnData);

                       }];

NSLog(@"Register URL: %@", url);
#endif
}
/**
* Failed to Register for Remote Notifications
*/
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
#if !TARGET_IPHONE_SIMULATOR
 NSLog(@"Error in registration. Error: %@", error);
#endif
}
/**
* Remote Notification Received while application was open.
*/

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
#if !TARGET_IPHONE_SIMULATOR

NSLog(@"remote notification: %@",[userInfo description]);

NSDictionary *apsInfo = [userInfo objectForKey:@"aps"];

NSString *alert = [apsInfo objectForKey:@"alert"];

NSLog(@"Received Push Alert: %@", alert);
NSString *sound = [apsInfo objectForKey:@"sound"];

NSLog(@"Received Push Sound: %@", sound);
//AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
NSString *badge = [apsInfo objectForKey:@"badge"];
NSLog(@"Received Push Badge: %@", badge);
 application.applicationIconBadgeNumber = [[apsInfo objectForKey:@"badge"] integerValue];

#endif
}
/*
*  END APNS CODE
-------------------------------------------------------
 */
- (void)applicationWillResignActive:(UIApplication *)application
{

// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.

// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

并且修改里面的push服务器地址

NSString *host = @"api.54im.com/pushchat";

现在可以把项目编译到iphone或者ipad上面了,注意项目 General中team配置。

![
![
![](http://upload-images.jianshu.io/upload_images/5220087-dc3dc5068d2a89c9.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
](http://upload-images.jianshu.io/upload_images/5220087-082f7287a6652996.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
](http://upload-images.jianshu.io/upload_images/5220087-5f97f03508dee99a.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)