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

附上原文地址 :http://blog.csdn.net/ghosc/article/details/7897201

原理

简单的说,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,代码如下:

/** *
 @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 bytesvoid

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)


 

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、付费专栏及课程。

余额充值