本文来自:瑞仙的Erlang开发博客
原文链接:http://blog.csdn.net/zhongruixian/article/details/39528765
一、前言
关于cer/pem证书转换,网上很多资料,我这就不说了,
网上有PHP及其他语言实现的Push Notification,可以参考,但大多都是版本OUT了,
为了更好的做PUSH服务定制,我这里以Erlang(gen_server)实现iOS Push Notification
二、协议
1、协议分析
名称 | 长度 | 描述 |
---|---|---|
Command | 1 字节 | 固定值(协议版本号):2 |
Frame length | 4 字节 | 上图所有的Item总字节数(注意:不是Item个数) |
Frame data | 变长 | 详见表2 |
名称 | 长度 | 描述 |
---|---|---|
Item ID | 1 字节 | 详见表3 |
Item data length | 2 字节 | Item data 字节数 |
Item data | 变长 | 详见表3 |
Item ID | 名称 | 长度 | Item data |
---|---|---|---|
1 | Device token | 32 字节 | 从客户端获取的device token(64字节的hex字符串) |
2 | 消息(payload) | <= 256 字节 | JSON格式,具体格式在这就不多说了,自己找吧
|
3 | 自定义的消息ID | 4 字节 | 返回错误时,会把这个值透传回来 |
4 | Expiration date | 4 字节 | UNIX时间戳
|
5 | Priority | 1 字节 | 传值10为立即发送 传值5为在Expiration date发送 |
2、协议数据打包实现
注意:网络字节序要有大端序
pack_frame(Cmd, Data) ->
Len = byte_size(Data),
<<Cmd:8, Len:16/big, Data/binary>>.
pack_frames(Frames) ->
FramesBin = list_to_binary(Frames),
FrameLen = byte_size(FramesBin),
<<2:8/big, FrameLen:32/big, FramesBin/binary>>.
%% 16进制字符串转binary
hex_to_bin(Data) when is_binary(Data) ->
hex_to_bin(binary_to_list(Data));
hex_to_bin(Data) ->
hex_to_bin(Data, []).
hex_to_bin([H1, H2 | T], Rt) ->
D1 = hex_to_oct(H1),
D2 = hex_to_oct(H2),
D = D1 bsl 4 + D2,
hex_to_bin(T, [<<D:8>> | Rt]);
hex_to_bin([], Rt) ->
list_to_binary(lists:reverse(Rt)).
hex_to_oct(N) when N >= 48, N =< 58 ->
N - 48;
hex_to_oct(N) when N >= 65, N =< 70 ->
N - 55;
hex_to_oct(N) when N >= 97, N =< 102 ->
N - 87.
三、PUSH实现过程
1、SSL启动
ssl:start()
2、SSL连接(gen_server代码片段)
-record(state, { ssl_socket }).
start_link() ->
gen_server:start_link({local, push}, ?MODULE, [], []).
init([]) ->
{ok, #state{}}.
handle_info(connect, State) ->
try
TcpOptions = [binary, {packet, 0}, {nodelay, true}, {delay_send, true}, {exit_on_close, false}],
SslOptions = [ {password, "1234"} ,{keyfile, "./dev_msttd_aps.pem"} ,{certfile, "./dev_msttd_aps.pem"},
Options = TcpOptions ++ SslOptions,
case ssl:connect("gateway.sandbox.push.apple.com", 2195, Options, 30000) of
{ok, Socket} ->
?INFO("~npush connect OK:~n~w", [Socket]),
{noreply, State#state{ssl_socket = Socket}};
{error, Error} ->
?INFO("~w", [Error]),
{noreply, State}
end;
catch T : X ->
?INFO("~p:~p~n~p", [T, X, erlang:get_stacktrace()]),
{noreply, State}
end;
3、向一个设备PUSH消息(gen_server代码片段)
handle_info({send, DeviceToken1}, State) ->
try
Time = util:unixtime(),
DeviceToken = hex_to_bin(DeviceToken1),
Payload = <<"{\"aps\":{\"alert\":\"《萌獸堂》萌獸等你來戰!\",\"sound\":\"default\"}}">>,
PayloadLen = byte_size(Payload),
Frame1 = pack_frame(1, DeviceToken),
Frame2 = pack_frame(2, Payload),
Frame3 = pack_frame(3, <<Time:32/big>>),
Frame4 = pack_frame(4, <<Time:32/big>>),
Frame5 = pack_frame(5, <<10:8/big>>),
Data = pack_frames([Frame1, Frame2, Frame3, Frame4, Frame5]),
case ssl:send(State#state.ssl_socket, Data) of
ok -> ?INFO("send ok (~s)", [DeviceToken1]);
Error -> ?INFO("send error:~p", [Error])
end
catch T : X ->
?INFO("~p:~p~n~p", [T, X, erlang:get_stacktrace()])
end,
{noreply, State};
4、错误处理(gen_server代码片段)
handle_info({ssl, _, <<8:8, StatusCode:8, CustomId:32/big>>}, State) ->
%% TODO: 出错啦,根据StatusCode来具体处理吧
?INFO("Error: ~w - ~w", [StatusCode, CustomId]),
{noreply, State};
Status code | Description |
---|---|
0 | 没有错误 |
1 | Processing error |
2 | Missing device token |
3 | Missing topic |
4 | Missing payload |
5 | Invalid token size |
6 | Invalid topic size |
7 | Invalid payload size |
8 | token无效 |
10 | Shutdown |
255 | None (unknown) |
四、使用实例
push ! {send, "8333801EBED044241D35C182F9F5CED47231D56A398AA1991060E3086699FE9F"}.