[轉]寶文!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

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM