如果你在给一个IOS应用添加聊天功能,如果你打算自己搭建它,并且进行离线消息的推送的话,你首先需要一个XMPP服务器。而ejabberd就是很优秀的一款,且很容易扩展其功能。

本文中,首先用ejabberd启动XMPP聊天服务,再编写一个ejabberd扩展模块将离线消息转发到我们的web服务器。之后你就可以将离线消息从web服务器传输到APN服务器,让Apple推送给用户客户端。

开发环境:CentOS6.5,ejabberd2.1.13,nodejs0.10.26

XMPP协议

XMPP(Extensible Messaging and Presence Protocol,前称Jabber)是一种以XML为基础的开放式实时通信协议,标准化为RTF3920。后来被Google Talk采用。

  • XMPP协议基于XML,可利用XML命名空间在核心协议基础上构建定制化功能,有很强的 扩展性
  • 同时其网络架构与邮件系统相似,属于 分散式 架构:利用TCP流通信而不需要中央服务器。
  • 其缺点也较显然:XML文本的通信方式与重复转发使其 数据负载太重
  • 同时 没有二进制数据 的支持(当然可以用base64来编码)。

安装ejabberd

ejabberd 是一款使用Erlang编写的开源IM服务器,支持XMPP协议。首先进行安装:

#在CentOS下可从yum源下载安装:
yum install ejabberd

启用web管理

ejabberd 支持网页管理,不过我们首先要添加管理员用户。首先添加一个域名:

%%% file: /etc/ejabbered/ejabberd.cfg

{hosts, ["localhost", "bix.org"]}

然后在该域名下添加一个用户harttle

# 启动ejabberd
ejabberdctl start       
# 注册用户
ejabberdctl register harttle bix.org [passwd]

将该用户设为管理员:

%% file: /etc/ejabbered/ejabberd.cfg

{acl, admin, {user, "harttle", "bix.org"}}.

重启后从浏览器打开:http://<IP>:5280/admin/,输入刚才的用户名密码即可进入ejabberd管理页面。

配置ejabberd

参照:官方文档

%%% file: /etc/ejabbered/ejabberd.cfg

%%% 默认配置只允许本机进行用户注册,我们注释掉它!
{mod_register,
%%%    {ip_access, [{allow, "127.0.0.0/8"},
%%%                 {deny, "0.0.0.0/0"}]},

...

%%% 禁用IP注册控制(默认10分钟内同一IP不可多次注册)
{registration_timeout, infinity}.

如此配置之后,已经可以通过ejabberdctl start启动XMPP服务了。可以通过web管理,客户端也可以进行正常的通信了。

离线转发

XMPP默认启用了离线消息模块,可以在服务器缓存消息,待客户端上线后一起发送。此外,我们还希望把离线消息推送给用户,以达到更好的用户友好性。

我们在此编写一个HTTP转发模块,将离线消息转发到web服务器。此后通过web服务器来进行手机端的推送。

ejabberd服务器的功能都是由模块提供的,模块定义在配置文件/etc/ejabberd/ejabberd.cfgmodules字段。这里是官方给出的模块贡献列表,下载后添加相应配置就可以用了。

为了创建这样一个模块,先创建如下的目录结构:

mod_offline_post/
    Emakefile
    src/
        mod_offline_post.erl
    ebin/

如下是Erlang语言的转发模块文件:

%% file: src/mod_offline_post.erl

%% name of module must match file name
-module(mod_offline_post).
 
-author("harttle").
 
%% Every ejabberd module implements the gen_mod behavior
%% The gen_mod behavior requires two functions: start/2 and stop/1
-behaviour(gen_mod).
 
%% public methods for this module
-export([start/2, stop/1, create_message/3]).
 
%% included for writing to ejabberd log file
-include("ejabberd.hrl").
 
%% ejabberd functions for JID manipulation called jlib.
-include("jlib.hrl").
 
start(Host, _Opt) -> 
        ?INFO_MSG("mod_offline_post loading", []),
        inets:start(),
        ?INFO_MSG("mod_offline_post HTTP client started", []),
        post_offline_message("testFrom", "testTo", "testBody"),
        ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, create_message, 10).   
 
stop (Host) -> 
        ?INFO_MSG("mod_offline_post stopping", []),
        ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE, create_message, 10).
 
create_message(_From, _To, Packet) ->
        ?INFO_MSG("mod_offline_post creating message",[]),
        Type = xml:get_tag_attr_s("type", Packet),
        FromS = xml:get_tag_attr_s("from", Packet),
        ToS = xml:get_tag_attr_s("to", Packet),
        Body = xml:get_path_s(Packet, [{elem, "body"}, cdata]),
        if (Type == "chat") ->
            post_offline_message(FromS, ToS, Body)
        end.
 
post_offline_message(From, To, Body) ->
        ?INFO_MSG("mod_offline_post posting from ~p to ~p body ~p~n",[From, To, Body]),
         http:request(post, {"http://localhost/xmppforward/offline",[], 
         "application/x-www-form-urlencoded",
         lists:concat(["from=", From,"&to=", To,"&body=", Body])}, [], []),
        ?INFO_MSG("mod_offline_post post request sent", []).

注意:ejabberd_hooks:add最后一个参数是优先级,值越小优先级越高,设置太大可能会使当前hook失效。

接下来便是编译这个模块了,注意编译时使用的erl要与ejabberd的Erlang版本一致(版本在官网可以查到)。编写一个Emakefile

%% file: Emakefile
{'src/mod_offline_post', [{outdir, "ebin"},{i,"/usr/lib64/ejabberd/include/"}]}.

开始编译!

# 编译
erl -pa . -pz ebin -make
# 安装
cp ./ebin/mod_offline_post.beam /usr/lib64/ejabberd/ebin
# 重启
ejabberdctl start
# 这样ejabberd就开始转发离线消息了

注意:可以用ejabberdctl live来让ejabberd交互式启动,并且输出所有log。

本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可: https://harttle.land/2014/10/17/xmpp-apn.html。如有疏漏、谬误、侵权请通过评论或 邮件 指出。