zoukankan      html  css  js  c++  java
  • [转]宝文!Apple Push Notification Service (APNS)原理与实现方案

    原理

    简单的说,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>
    #include <netinet/in.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; }

    编译运行:

    g++ main.cpp -o pushServer -lssl -lcrypto
    

      

  • 相关阅读:
    3名百度 ,京东,腾讯被辞退的高级Android工程师现在怎么了?30岁真的是“罪”吗
    Python 开发者在迁移到 Go 前需要知道的事情
    centos7 常用命令--查看当前用户的4种方法
    Centos7找不到ifconfig和netstat命令
    Centos 7 修改日期和时间的命令
    如何利用MobaX同时处理多台虚拟机输入相同命令如何利用MobaX同时处理多台虚拟机输入相同命令
    配置坑了我好久:Logback按天生成文件失效
    quartz系列文章
    SpringBoot使用多实例QUARTZ出现重复执行问题
    IDEA多个springboot项目启动修改端口
  • 原文地址:https://www.cnblogs.com/lihaozy/p/2931860.html
Copyright © 2011-2022 走看看