Apple Push Notification Service (APNS)原理与实现方案

http://maxwin.me/blog/?p=170

 

原理

简单的说,app要单独实现消息动态更新,一种是轮询,这对用户来说会带来额外的流量。另一种方案是push,app client和server直接保持一个长连接,有新的消息时server push给app client。

这两种通过app自身实现的“push”都会面临以下问题:

  1. 进程被关闭后无法在发送请求
  2. 大量app开启网络连接对用户的流量和电池都会带来大量消耗
  3. 通讯安全问题需要app自己提供解决方案

APNS为app开发商提供了一个统一的消息通知平台。apple和每个iOS设备之间保持一个长连接。开发商将消息发送给APNS,APNS将该消息发送到指定的iOS设备,iOS设备展示消息、启动相应的app。


从以上过程可知,为通过APNS实现消息push,一个消息需要标识发送到“哪台iOS设备”的“哪个app”。这两个分别由设备id(deviceToken)和SSL的证书文件(开启消息push的app需要配置一个SSL证书)标识。消息结构体如下:

实现方案

详细过程请参考http://mobiforge.com/developing/story/programming-apple-push-notification-services,一步步来即可。这一过程会生成文件:

  1. CertificateSigningRequest.certSigningRequest 用于生成SSL证书的中间文件,用后可删除
  2. aps_developer_identity.cer SSL证书文件,这一证书会关联app id,利用该证书可将消息发送到对应的app。
  3. [xxx].mobileprovision和上述SSL证书文件绑定的app provision文件,请安装到测试设备。

双击aps_developer_identity.cer将证书文件导入到Keychain Access。

java/php/c++需要的SSL证书和密钥生成如下:

  1. 将keychain access中的aps_developer_identity.cer导出为[xxx].p12
  2. 终端下执行:
    1
    openssl pkcs12 - in [xxx].p12 -out [xxx].pem -nodes

将最终生成的pem文件提供给后台server

后台代码示例

app client请参考上述链接中的示例。

push server:

object c版本 http://stefan.hafeneger.name/download/PushMeBabySource.zip

java版本 http://code.google.com/p/javapns/

php版本 http://code.google.com/p/apns-php/

现在后台技术主要还是c/c++吧,网上一直没找到合适的,自己实现demo,代码如下:

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/**
  * @file main.cpp
  * @author Maxwin
  * @description TestPushServer
  */
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/x509.h>
 
// 证书文件
#define CERTFILE "max.pem"
 
SSL *ssl;
SSL_CTX *ctx;
 
void error( const char *msg)
{
     printf ( "[ERROR]:%s\n" , msg);
     exit (1);
}
 
SSL_CTX *setup_client_ctx()
{
     ctx = SSL_CTX_new(SSLv23_method());
     if (SSL_CTX_use_certificate_chain_file(ctx, CERTFILE) != 1) {
         error( "Error loading certificate from file\n" );
     }
     if (SSL_CTX_use_PrivateKey_file(ctx, CERTFILE, SSL_FILETYPE_PEM) != 1) {
         error( "Error loading private key from file\n" );
     }
     return ctx;
}
 
// 将deviceToken字符串转成对应的binary bytes
void token2bytes( const char *token, char *bytes)
{
     int val;
     while (*token) {
         sscanf (token, "%2x" , &val);
         *(bytes++) = ( char )val;
 
         token += 2;
         while (*token == ' ' ) { // skip space
             ++token;
         }
     }
}
 
// 打包消息
unsigned long packMessage( char *message, const unsigned char command, const char *tokenBytes, const char *payload)
{
     unsigned long payloadLength = strlen (payload);
     unsigned short networkTokenLength = htons(32);
     unsigned short networkPayloadLength = htons(payloadLength);
 
     memcpy (message, &command, sizeof (unsigned char ));
     message += sizeof (unsigned char );
     memcpy (message, &networkTokenLength, sizeof (unsigned short ));
     message += sizeof (unsigned short );
     memcpy (message, tokenBytes, 32);
     message += 32;
     memcpy (message, &networkPayloadLength, sizeof (unsigned short ));
     message += sizeof (unsigned short );
     memcpy (message, payload, payloadLength);
 
     return payloadLength + 37;
}
 
int push( const char *token, const char *payload)
{
     char tokenBytes[32];
     char message[293];
     unsigned long msgLength;
 
     token2bytes(token, tokenBytes);
     msgLength = packMessage(message, 0, tokenBytes, payload);
     return SSL_write(ssl, message, ( int )msgLength);
}
 
int main ( int argc, const char * argv[])
{
     char token[] = "2b2474e5 ac7670f3 08fabf3a 9c1d1295 ed50e9aa f11b941a d6e3d213 4f535408" ;
     char payload[] = "{\"aps\":{\"alert\":\"Hello world!!!\",\"badge\":1}}" ;
     char payload2[] = "{\"aps\":{\"alert\":\"Hello kitty!!!\",\"badge\":12}}" ;
 
     char host[] = "gateway.sandbox.push.apple.com:2195" ;
 
     BIO *conn;
 
     // init
     SSL_library_init();
     ctx = setup_client_ctx();
     conn = BIO_new_connect(host);
     if (!conn) {
         error( "Error creating connection BIO\n" );
     }
 
     if (BIO_do_connect(conn) <= 0) {
         error( "Error connection to remote machine" );
     }
 
     if (!(ssl = SSL_new(ctx))) {
         error( "Error creating an SSL contexxt" );
     }
 
     SSL_set_bio(ssl, conn, conn);
     if (SSL_connect(ssl) <= 0) {
         error( "Error connecting SSL object" );
     }
 
     printf ( "SSL Connection opened\n" );
 
     // push message
     int ret = push(token, payload);
     printf ( "push ret[%d]\n" , ret);
 
     // push [Hello kitty] after 5s
     sleep(5);
     ret = push(token, payload2);
     printf ( "push2 ret[%d]\n" , ret);
 
     printf ( "Close SSL Connection\n" );
 
     SSL_shutdown(ssl);
     SSL_free(ssl);
     SSL_CTX_free(ctx);
 
     return 0;
}

编译运行:

1
g++ main.cpp -o pushServer -lssl -lcrypto
Views: (715)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Apple APNs java client, based on netty4. 基于netty4实现的苹果通知推送服务Java客户端。 特点: 支持第三版通知推送,即command = 2。目前的绝大部分Java客户端都只支持command = 1,即第二版。 支持SSL握手成功才返回,可以调用 pushManager.start().sync(); 等待握手成功才开始发送; 最大限度重试发送,内部自动处理重连,错误重发机制; 支持配置RejectListener,即通知被Apple服务器拒绝之后的回调接口; 支持配置ShutdownListener,即当shutdown时,没有发送完的消息处理的回调接口; 支持发送统计信息; 实现组件分离,可以利用PushClient,FeedbackClient来写一些灵活的代码。 Notification发送者可以自己定义设置发送的Queue,自己灵活处理阻塞,超时等问题。     Example: 更多的例子在src/test/java 目录下。 public class MainExample {     public static void main(String[] args) throws InterruptedException {         Environment environment = Environment.Product;         String password = "123456";         String keystore = "/home/hengyunabc/test/apptype/app_type_1/productAPNS.p12";         PushManager pushManager = new PushManagerImpl(keystore, password, environment);         //set a push queue         BlockingQueuequeue = new LinkedBlockingQueue(8192);         pushManager.setQueue(queue );         //waiting for SSL handshake success         pushManager.start().sync();         //build a notification         String token = "5f6aa01d8e3358949b7c25d461bb78ad740f4707462c7eafbebcf74fa5ddb387";         Notification notification = new NotificationBuilder()                 .setToken(token)                 .setBadge(1)                 .setPriority(5)                 .setAlertBody("xxxxx").build();         //put notification into the queue         queue.put(notification);         TimeUnit.SECONDS.sleep(10);         //get statistic info         Statistic statistic = pushManager.getStatistic();         System.out.println(statistic);     } } 标签:zpush

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值