ios 推送的具体实现 PHP代码
该文件为 app推送架设 里的ios推送部分
在linux的 crontab 定时计划里 加入
1
|
* * * * *
/usr/local/php/bin/php
/www/push/ios/ios_push
.php app1_ios 48
|
第一个参数 app1_ios
标识app1应用,memcacheq队列里面等待推送的ios队列名也是app1_ios 在app推送架设里就是ios_go所代表的那些中的一个队列
第二个参数 48
与苹果建立一次ssl链接时要推送的消息数。PS:至于为什么是48(稍后解释)。
ios_push.php文件
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
#!/usr/local/php/bin/php
<?PHP
date_default_timezone_set(
'Asia/Shanghai'
);
set_time_limit(0);
/**
* 推送器ios模块
* @param string $argv[1] memcacheq 键值
*/
$mem_key
=
$argv
[1];
$key_count
=
$argv
[2];
if
(
empty
(
$mem_key
))
{
exit
(
'first parameter is a must'
);
}
if
(
empty
(
$key_count
))
{
exit
(
'second parameter is a must'
);
}
if
(
$key_count
>500||
$key_count
<1){
exit
(
'error second parameter'
);
}
//配置文件ios推送所需的pem文件,与该php文件在同一目录下
$pem_config
=
array
(
'app1_ios'
=>
'ck1_pro.pem'
,
'app2_ios'
=>
'ck2_pro.pem'
,
);
$pem_password
=
array
(
'ck1_pro.pem'
=>
'password'
,
'ck2_pro.pem'
=>
'password'
,
);
$ck_pem
=
$pem_config
[
$mem_key
];
if
(
empty
(
$ck_pem
))
{
exit
(
'error pem name'
);
}
$ck_pem_password
=
$pem_password
[
$ck_pem
];
//执行推送
run(
$mem_key
,
$ck_pem
,
$ck_pem_password
,
$key_count
);
//用while死循环,来提高推送频率,间隔5秒。(可根据实际情况而定)
//createPidFile和deletePidFile 用来确保同一时刻只存在一个进程在执行推送php
function
run(
$mem_key
,
$ck_pem
,
$ck_pem_password
,
$key_count
)
{
if
(createPidFile(__DIR__.
'/../logs/cron_'
.
$mem_key
.
'_ios.pid'
))
{
exit
();
}
$ctx
= stream_context_create ();
$push_file
=dirname(
__FILE__
) .
'/'
.
$ck_pem
;
stream_context_set_option (
$ctx
,
'ssl'
,
'local_cert'
,
$push_file
);
stream_context_set_option (
$ctx
,
'ssl'
,
'passphrase'
,
$ck_pem_password
);
while
(TRUE)
{
try
{
//具体执行推送任务函数
push_msg(
$mem_key
,
$key_count
,
$ctx
);
sleep(5);
}
catch
(Exception
$e
)
{
cron_error(
$mem_key
,
date
(
"[Y-m-d H:i:s] "
, time()) .
$e
->getMessage() .
"\n"
);
sleep(5);
}
}
deletePidFile(__DIR__.
'/../logs/cron_'
$mem_key
'_ios.pid'
);
}
function
push_msg(
$mem_key
,
$key_count
,
$ctx
)
{
$time
=time();
//苹果推送测试地址
// $fp = stream_socket_client ( 'ssl://gateway.sandbox.push.apple.com:2195', $errno, $errstr, 50, STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_CONNECT, $ctx );
//苹果推送正式地址
$fp
= stream_socket_client (
'ssl://gateway.push.apple.com:2195'
,
$errno
,
$errstr
, 50, STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_CONNECT,
$ctx
);
if
(!
$fp
)
{
echo
"Unable to connect: ios-APNS \n"
;
}
else
{
//开始连接推送memcacheq队列
$m
=
new
memcache();
$m
->connect(
'127.0.0.1'
,22201);
for
(
$i
=0;
$i
<
$key_count
;
$i
++)
{
$push_body
=
$m
->get(
$mem_key
);
//从推送队列取出的推送体push_body,详见底部PS:1
if
(
is_string
(
$push_body
))
{
$push_body
=unserialize(
$push_body
);
}
if
(
empty
(
$push_body
))
{
fclose (
$fp
);
break
;
}
//组装实际推送的消息body,详见底部PS:2
$body
=
array
();
$body
[
'aps'
] =
array
(
'alert'
=>
$push_body
[
'content'
] );
//推送信息内容
$body
[
'msg_type'
] =
$push_body
[
'push_type'
];
//推送信息类型
if
(
$push_body
[
'push_type'
]==2)
//带链接推送
{
$body
[
'url'
] =
$push_body
[
'msg_link'
];
}
//去除设备token里面首尾多余的符号
$token
=trim(trim(
$push_body
[
'device_code'
],
"<"
),
">"
);
$payload
= json_encode (
$body
);
$msg
=
chr
( 0 ) . pack (
"n"
, 32 ) . pack (
'H*'
,
str_replace
(
' '
,
''
,
$token
) ) . pack (
"n"
,
strlen
(
$payload
) ) .
$payload
;
//ssl链接因为某些原因断开,该推送消息从新回到等待推送队列里面,并记录错误日志。
if
(!fwrite (
$fp
,
$msg
))
{
$m
->set(
$mem_key
,
$push_body
);
log_error(
$push_body
,
$mem_key
,
$i
);
break
;
}
//每个完成推送的消息将附上时间和目标移动设备在app系统的唯一标识ID,然后存储进推送完毕队列
$push_body
[
'push_time'
]=
$time
;
$push_body
[
'device_id'
]=
intval
(
$push_body
[
'device_id'
]);
$m
->set(
$mem_key
.
'_end'
,
$push_body
);
}
fclose (
$fp
);
}
}
//定时错误日志
function
cron_error(
$mem_key
,
$msg
)
{
$file
= __DIR__.
'/../logs/'
.
$mem_key
.
'_cron_error_'
.
date
(
"Y-m-d"
) .
'.log'
;
error_log
(
$msg
, 3,
$file
);
}
//传输错误日志
function
log_error(
$push_body
,
$mem_key
,
$i
)
{
$file
= __DIR__.
'/../logs/'
.
$mem_key
.
'_error_'
.
date
(
"Y-m-d"
) .
'.log'
;
$str
=
'[ '
.
date
(
'Y-m-d H:i:s'
,time()) .
' ] ['
.
$i
.
'] [ '
.
$push_body
[
'app_type'
].
' - device_id:'
.
$push_body
[
'device_id'
].
' ] [ '
.
$push_body
[
'device_code'
].
' ]'
.
"\n"
;
error_log
(
$str
, 3,
$file
);
}
//将当前php文件执行进程写进pid文件
function
createPidFile(
$pid_filename
)
{
if
(
file_exists
(
$pid_filename
))
{
$old_pid
= trim(
file_get_contents
(
$pid_filename
));
if
(
file_exists
(
'/proc/'
.
$old_pid
.
'/cmdline'
))
{
return
TRUE;
}
}
$pid
= posix_getpid();
$pid
=
strval
(
$pid
);
file_put_contents
(
$pid_filename
,
$pid
);
return
FALSE;
}
//删除pid文件
function
deletePidFile(
$pid_filename
)
{
@unlink(
$pid_filename
);
}
?>
|
PS:
1、从推送队列取出的推送体push_body
可能直接是个数组,也可能个字符串。这取决于你存入memecacheq时所处的位置。
(1)若是本台服务器代码组装push_body存入本身的memcacheq,那么取出时将直接是个数组。
(2)若是跨服务器的传输memcacheq,那么取出是将是个数组字符串化,需要unserialize。
2、组装实际推送的消息body
(1)$body['aps']=array('alert' =>'xxxx') 为固定的数组格式
(2)$body ['msg_type'] 和 $body ['url'] 为与ios App开发人员商定的其他参数,用于打开链接等等
紧急代码修正: 2013-09-03
将原来位于function push_msg里面的代码
1
2
3
4
|
$ctx
= stream_context_create ();
$push_file
=dirname(
__FILE__
) .
'/'
.
$ck_pem
;
stream_context_set_option (
$ctx
,
'ssl'
,
'local_cert'
,
$push_file
);
stream_context_set_option (
$ctx
,
'ssl'
,
'passphrase'
,
$ck_pem_password
);
|
向上提至function run里面,置于while循环之前
注:
运行环境:8核16G | centOS (单台)
开启ios推送进程45个,日推送量在60w条,全部集中在一个时间段内(1小时之内)
在长达2个月的运行中,发现该运行脚本运行7天单进程所耗内存达到0.5%,45个进程就是22.5%,即3600M的内存。而且还在持续增加。
单脚本进程每一个while循环之后,内存消耗会增加1080B,久而久之就是一个巨大的消耗,随着内存的不够使用,还将导致推送的推送量下降。
后经过排查:主要在stream_context_create ()函数所耗,即使unset($ctx)和fclose ( $fp )也无法释放,结合上述4行代码是一个固定的结果,所以提至while循环函数之外。
至此,该脚本内存消耗量不在增加。