Push_Notification(一)

推送原理


苹果信息推送服务(Apple Push Notification Service),是由苹果官方提供的消息推送服务。推送形式包括顶部消息条、声音以及badge number()有了APNS,应用程序可在任意状态接收到与程序有关的消息(包括运行状态not running,foreground以及background),由于在大多数情况下,iOS中最多只有一个应用能处于active状态,所以,APNS为应用的交互提供了极大的便利。

image

  1. 应用程序注册消息推送。(就是比较复杂的制作证书的过程)
  2. IOS跟APNS Server要deviceToken。应用程序接受deviceToken。(在Xcode中编码实现)
  3. 应用程序将deviceToken发送给服务端程序。(服务端程序为图中的“Your Server”,可能是我们的signaling或者weixin之一,还要和服务器的同事商量,不确定用哪个合适)
  4. Your Server向APNS服务器发送信息。
  5. APNS服务将推送内容发送给iPhone应用程序。(APNS :Apple Push Notification Server)

其中几条已经做了注解,其中第1~3步是由iOS端的人员负责的:关于如何制作证书、如何获取device token、制作和获取的过程中会遇到哪些问题及解决方法,下一篇博客来写,这一篇呢,我们只讲道理~~。第4步是服务器端人员负责。第5步是苹果的事情。关于上面过程中标记为斜体的部分分别都来解释一下:

证书

作用:让APNS能够知道应该给哪个App推送消息!

详细:此处的证书是苹果开发者利用其在苹果公司注册的开发权限申请并制作的,难道不是只要在开发者网站点击一下类似“申请”按键就OK吗?还真就不是!当iOS App某些功能想要和苹果提供的特殊服务(如Game Center、Wallet、健康数据)结合的时候,都需要另外申请证书,我们现在说的push notification,也是多个特殊服务中的一个。这个证书还要和App信息、开发者信息,利用openssl命令合成一个 *.p12格式的证书文件(这个文件后面用)

device token

作用:让APNS能够知道应该给哪个device推送消息!

详细:这个device token在每次一个App启动的时候都会向苹果申请,不出意外的话,都会返回一个device token,在之前说明push过程时,它的作用也已经知道了,不过呢,关于它有几个值得疑问的地方:

  1. device token是啥?
  2. 不同的App,device token相同么?
  3. 一个设备会产生多个device token么?
  4. device token会失效么?
  5. device token和UDID是一回事么?(注意不是UUID,两者区别请看:UUID与UDID)
  6. 一个UDID可能对应多个device token么?反之的情况呢?

结论:

  1. device token 是标记设备的,和app无关(解答问题0,1);
  2. 一个设备的device
    token 一般是不变的,也就是不会失效,但是如果设备擦除(官方词汇是be wiped),出于安全考虑,token必须变(解答问题2,3),而UDID是不变的(解答问题4);
  3. 关于问题5,实际当中两种情况都会出现,也的确有过push混乱的事故,但是我们暂时先放下不管,因为我们可以拿出一些资源来动态处理这个问题,之后提出。实际上有人做过实验,同一个udid对应有不同的device token的情况暂时有如下两种:

    1. 设备擦除过,token变化过,老的新的都存储在数据库里
    2. 设备同时装过development(测试版)和distribution(发布版)的程序。

信息

这个要向APNS信息包括 part1:device token + .p12证书文件+证书文件的密码,起到识别设备和App的作用;part2:具体推送的内容,即设备上的显示内容。证书文件的密码,是建立这个 .p12证书文件时设置的,iOS端人员可以提供。在下面“服务器要做的事情”中会详细说。

App服务端要做的事


其实基本上已经说明了,这里只是单列出来,并附上服务端参考程序。

  1. 服务端的作用是向苹果的APNS发送推送请求,告诉苹果:我知道你是大爷,那你帮忙给我家应用推个消息呗?哪个设备就看device token,哪个应用就看p12证书,p12证书的密码啥的我都给你提供,您老看成不?
  2. device token 由iOS客户端提供,服务器负责处理,处理的内容可能包括(主要问题都集中在这里,请看一下):

    1. 把从客户端接收到的device token和当前用户的信息在数据库中建立关联,保证在TV–>iOS拨打时(即图中所说的when something interesting happens),能够找到正确的device token;
    2. 新建一份协议来接收device token,当接收并储存成功后,向客户端返回诸如_devicetoken的关键字,表示已成功接收;
    3. 对于一台iOS设备,其device token 不会轻易改变,但是为了应对device token改变的特殊情况,是否每次客户端程序启动都需要向服务端发送一遍token,服务端重新保存一遍?显然不好!目前想到的策略是,客户端得到该设备的最新device token后会与上一次得到的进行对比,如不同,则把新的token发给服务器,服务器收到后,替换一下之前的无效token即可;如相同,则不发送给服务器,服务器也就无需处理,以减轻压力。
  3. *.p12证书文件+证书文件的密码:这两个也是客户端人员制作好提供给服务端,在服务端程序中直接用就好。但是证书文件分为开发版(development)和产品版(distribution),目前测试用的是开发版,以后正式发布后会重新制作证书。

  4. 具体推送的内容:就是设备推送界面的具体内容了,如“ **想要和你进行视频通话”,也要在服务器设定好。

服务器代码


服务端代码每种语言都有好多个版本,之前也是废了好大劲儿,才找到能用的版本,基本上都用到了各自语言下第三方库。

  1. Objective-C版

    大家都说这个好。

  2. PHP版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php
// Put your device token here (without spaces):
$deviceToken = '64位十六进制数';//自己的device token

// Put your private key's passphrase here:
$passphrase = '证书的密码';//自己的证书密码

// Put your alert message here:
$message = '推送内容!';

////////////////////////////////////////////////////////////////////////////////

$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', '*.pem');//*.pem换成自己的证书,且必须为pem格式的
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);

// Open a connection to the APNS server
$fp = stream_socket_client(
'ssl://gateway.sandbox.push.apple.com:2195', $err,
$errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);

if (!$fp)
exit("Failed to connect: $err $errstr" . PHP_EOL);

echo 'Connected to APNS' . PHP_EOL;

// Create the payload body
$body['aps'] = array(
'alert' => $message,
'sound' => 'default'
);

// Encode the payload as JSON
$payload = json_encode($body);

// Build the binary notification
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;

// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));

if (!$result)
echo 'Message not delivered' . PHP_EOL;
else
echo 'Message successfully delivered' . PHP_EOL;

// Close the connection to the server
fclose($fp);

?>

中文的地方要自己替换,且注意注释部分,php版用另一方法制作的.pem文件作为证书,也是证书的一种格式,我试了,这里用.p12文件不好使,只能用这个格式,不知道为啥。

  1. Java版
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import java.util.ArrayList;
import java.util.List;

import javapns.notification.AppleNotificationServer;
import javapns.notification.AppleNotificationServerBasicImpl;
import javapns.notification.PayloadPerDevice;
import javapns.notification.PushNotificationPayload;
import javapns.notification.transmission.NotificationProgressListener;
import javapns.notification.transmission.NotificationThread;
import javapns.notification.transmission.NotificationThreads;

public class testClass {
public static void main(String[] args){
String keystore = "cer/aps_developer_identity.p12";//p12证书路径和证书名
String password = "证书密码"; // 证书密码
String token = "64位十六进制数";// device token
boolean production = false; // 设置true为正式服务地址,false为开发者地址
int threadThreads = 10; // 线程数
try {
// 建立与Apple服务器连接
AppleNotificationServer server = new AppleNotificationServerBasicImpl(keystore, password, production );
List<PayloadPerDevice> list = new ArrayList<PayloadPerDevice>();
PushNotificationPayload payload = new PushNotificationPayload();
payload.addAlert("TV请求视频通话");
payload.addSound("default");// 声音
payload.addBadge(1);//图标小红圈的数值
//payload.addCustomDictionary("url","www.baidu.com");//添加字典用途不明暂时干掉,干掉后测试没有影响结果
PayloadPerDevice pay = new PayloadPerDevice(payload,token);// 将要推送的消息和手机唯一标识绑定
list.add(pay);

NotificationThreads work = new NotificationThreads(server,list,threadThreads);//
work.setListener(DEBUGGING_PROGRESS_LISTENER);// 对线程的监听,一定要加上这个监听
work.start(); // 启动线程
work.waitForAllThreads();// 等待所有线程启动完成

} catch (Exception e) {
e.printStackTrace();
}
}

public static final NotificationProgressListener DEBUGGING_PROGRESS_LISTENER = new NotificationProgressListener() {
public void eventThreadStarted(NotificationThread notificationThread) {
System.out.println(" [EVENT]: thread #" + notificationThread.getThreadNumber() + " started with " + " devices beginning at message id #" + notificationThread.getFirstMessageIdentifier());
}
public void eventThreadFinished(NotificationThread thread) {
System.out.println(" [EVENT]: thread #" + thread.getThreadNumber() + " finished: pushed messages #" + thread.getFirstMessageIdentifier() + " to " + thread.getLastMessageIdentifier() + " toward "+ " devices");
}
public void eventConnectionRestarted(NotificationThread thread) {
System.out.println(" [EVENT]: connection restarted in thread #" + thread.getThreadNumber() + " because it reached " + thread.getMaxNotificationsPerConnection() + " notifications per connection");
}
public void eventAllThreadsStarted(NotificationThreads notificationThreads) {
System.out.println(" [EVENT]: all threads started: " + notificationThreads.getThreads().size());
}
public void eventAllThreadsFinished(NotificationThreads notificationThreads) {
System.out.println(" [EVENT]: all threads finished: " + notificationThreads.getThreads().size());
}
public void eventCriticalException(NotificationThread notificationThread, Exception exception) {
System.out.println(" [EVENT]: critical exception occurred: " + exception);
}
};
}

用的证书是p12格式的,其他格式也不好使,java用了javapns框架来实现这个功能,为了具有代表性,我放了一个多线程的版本,下半部分代码负责进城管理,
需要用到的包有

  1. JavaPNS2.1.jar
  2. bcprov-jdk15-146.jar
  3. log4j-1.2.15.jar
  4. commons-io-2.0.1.sources.jar
  5. commons-lang-2.6.jar
    为了方便,我把这些包放在云盘里了,提取码:76ss

参考


证书制作请参考此篇中的相关部分。在java版本中使用的是p12文件,最终交给服务器使用也是这个文件,如果是java参考这一篇就够了

如果服务器是PHP,那么需要将p12文件继续封装成ck12,
将证书封装为ck12文件的过程,参考此篇 中的相关部分。

基础知识以及各种类型证书的区别,参考此篇

done!

那强 wechat
加个微信 成为朋友吧