XMPP ejabberd_listener 代码分析

DavidAlphaFox · 发布于 2017年09月23日 · 290 次阅读
84794b

ejabberd_listener简介

ejabberd_listener模块,虽然是一个文件。但是,它包含了两个部分,第一部分是ejabberd_listener作为监控树进程启动,第二部分是作为这个监控树的子进程启动端口监听。

ejabberd_listener模作为监控树进程启动的时候,是作为ejabberd_sup监控树中的子进程启动的。该进程启动后会在本地注册为ejabberd_listeners进程,是一个one_for_one模式的空监控树为后面启动各种各样的端口监听进程准备的。可以参考ejabberd_sup.erl中的代码。

ejabberd_listener模块作为ejabberd_listeners监控树的子进程启动时,是在eJabberd整个应用启动的最后一步调用ejabberd_listener:start_listeners()完成的。可以参考ejabberd_app.erl中的代码。

ejabberd_listener模块的大量代码都是为端口监听服务的,我们就具体分析下这些代码。

ejabberd_listener开启监听

start_listeners() ->
  case ejabberd_config:get_local_option(listen) of
    undefined ->
      ignore;
    Ls  ->
       Ls2 = lists:map(
          fun({Port, Module, Opts})  ->
             case start_listener(Port, Module, Opts) of
                {ok, _Pid} = R  -> R;
                {error, Error}  -> throw(Error)
           end
        end, Ls),
       report_duplicated_portips(Ls),
      {ok, {{one_for_one, 10, 1}, Ls2}}
   end.

从这段代码可以看出,ejabberd_listener:start_listeners()会从配置中找出所有的监听模块并通过start_listener函数逐个启动。start_listener通过调用start_listener2进而调用连个比较核心的函数start_module_sup和start_listener_sup来完成端口监听进程的启动。

start_module_sup(_PortIPProto, Module) ->
    Proc1 = gen_mod:get_module_proc("sup", Module),
    ChildSpec1 =
        {Proc1,
         {ejabberd_tmp_sup, start_link, [Proc1, Module]},
         permanent,
         infinity,
         supervisor,
         [ejabberd_tmp_sup]},
    supervisor:start_child(ejabberd_sup, ChildSpec1).

start_module_sup使用gen_mod来生成监控树进程的名字,使用ejabberd_tmp_sup模块作为代码载体,创建一个监控进程在ejabberd_sup下启动。例如,ejabberd_c2s作为Module,那么Proc1就是ejabberd_c2s_sup。由于ejabberd_tmp_sup在构建监控树的时候,使用的是simple_one_for_one模式,所以在ejabberd_c2s_sup这个监控进程被创建成功后并不会创建一个ejabberd_c2s的进程。

start_listener_sup(PortIPProto, Module, Opts) ->
    case Module:socket_type() of
        independent ->
            Module:start_listener(PortIPProto, Opts);
        _ ->

            ChildSpec = {PortIPProto,
                         {?MODULE, start, [PortIPProto, Module, Opts]},
                         transient,
                         brutal_kill,
                         worker,
                         [?MODULE]},
            supervisor:start_child(ejabberd_listeners, ChildSpec)
    end.

start_listener_sup函数首先会判断Module的端口类型,如果是独立监听的模块,那么就让模块通过start_listener来自行启动监听,如果是其它类型的,都认为是可以使用ejabberd_listener作为监听模块来进行监听。直接将ejabberd_listener模块作为ejabberd_listeners的子进程启动起来。

ejabberd_listener进行监听

ejabberd_listener是支持TCP端口监听和UDP端口监听的,这里重点分析下ejabberd_listener针对TCP的accept操作。

accept(ListenSocket, Module, Opts) ->
    case gen_tcp:accept(ListenSocket) of
        {ok, Socket} ->
            case {inet:sockname(Socket), inet:peername(Socket)} of
                {{ok, Addr}, {ok, PAddr}} ->
                    ?INFO_MSG("(~w) Accepted connection ~w -> ~w",
                              [Socket, PAddr, Addr]);
                _ ->
                    ok
            end,
            ejabberd_socket:start(Module, gen_tcp, Socket, Opts),
            ?MODULE:accept(ListenSocket, Module, Opts);
        {error, Reason} ->
            ?INFO_MSG("(~w) Failed TCP accept: ~w",
                      [ListenSocket, Reason]),
            ?MODULE:accept(ListenSocket, Module, Opts)
    end.

accept函数在gen_tcp进行accept成功后,先获取对端的地址,并交付给ejabberd_socket:start函数去关联数据接收进程和协议进程。而accept函数,就继续循环调用gen_tcp的accept来接入连接。

自定义传输方式

假设我们需要在XMPP协议外面封装一层其它的协议,例如说STOMP,应该如何实现自己的监听和数据解析呢?

有以下两种方式实现:

1.使用independent方式,实现监听和协议解析,可以参考ejabberd_cowboy.erl。

2.使用raw方式,让ejabberd_listener完成监听,自定义模块进行协议解析,可以参考ejabberd_socket.erl

不管使用哪种方式都需要实现关键函数socket_type。剩下关于自定义传输方式模块的编写,将在后面的博客进一步介绍。

备注

在MongooseIM中Webosocket和BOSH都被实现了。所以在有需要使用Websocket的生产环境中,博主非常推荐使用MongooseIM来代替社区版本ejabberd。

暂无回复。
需要 登录/注册 后方可回复, 如果你还没有账号请点击这里 注册