http://maxwin.me/blog/?p=170
原理
简单的说,app要单独实现消息动态更新,一种是轮询,这对用户来说会带来额外的流量。另一种方案是push,app client和server直接保持一个长连接,有新的消息时server push给app client。
这两种通过app自身实现的“push”都会面临以下问题:
- 进程被关闭后无法在发送请求
- 大量app开启网络连接对用户的流量和电池都会带来大量消耗
- 通讯安全问题需要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,一步步来即可。这一过程会生成文件:
- CertificateSigningRequest.certSigningRequest 用于生成SSL证书的中间文件,用后可删除
- aps_developer_identity.cer SSL证书文件,这一证书会关联app id,利用该证书可将消息发送到对应的app。
- [xxx].mobileprovision和上述SSL证书文件绑定的app provision文件,请安装到测试设备。
双击aps_developer_identity.cer将证书文件导入到Keychain Access。
java/php/c++需要的SSL证书和密钥生成如下:
- 将keychain access中的aps_developer_identity.cer导出为[xxx].p12
- 终端下执行:
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
|