Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5120012
  • 博文数量: 921
  • 博客积分: 16037
  • 博客等级: 上将
  • 技术积分: 8469
  • 用 户 组: 普通用户
  • 注册时间: 2006-04-05 02:08
文章分类

全部博文(921)

文章存档

2020年(1)

2019年(3)

2018年(3)

2017年(6)

2016年(47)

2015年(72)

2014年(25)

2013年(72)

2012年(125)

2011年(182)

2010年(42)

2009年(14)

2008年(85)

2007年(89)

2006年(155)

分类: Python/Ruby

2012-08-22 10:36:52

Erlang messaging server optimized to send 1 message to 40k subscribers to a topic in < 1s

下载地址:

启动过程:

make run1

run1在MakeFile中的定义:

  1. run1: compile
  2.     erl $(LOCAL_OPTS) -name $(NODE) -s janus start

janus.erl内容:

  1. start() ->
  2.     start([]).

  3. start([]) ->
  4.     start(8081);

  5. start([Port])
  6.   when is_atom(Port) ->
  7.     start(list_to_integer(atom_to_list(Port)));

  8. start(Port)
  9.   when is_integer(Port) ->
  10.     inets:start(),
  11.     application:set_env(janus, listen_port, Port),
  12.     application:start(janus).

调用了application:start/1  方法,这样会启动一个erlang 应用,应用程序的定义在同名的janus.app中:

直接看janus.app中最后的回调模块部分:

  1. {mod, {janus_app, []}},


系统会调用janus_app:start/2

所以查看janus_app.erl中的代码:

  1. start(_Type, _Args) ->
  2.     Port = janus_admin:get_env(listen_port, ?LISTEN_PORT),
  3.     supervisor:start_link({local, ?MODULE},
  4.                           ?MODULE,
  5.                           [Port, transport]).

获取了服务器端监听的端口,然后启动了一个Supervisor,传入的参数为[ Port,transport ]

所以看init/2 这个初始化方法:

启动了一系列子进程,先看第一个:

  1. %% TCP server
  2.        {janus_sup, %% ID名
  3.         {janus_acceptor, start_link, [self(), Port, Module]}, %%启动时调用的方法janus_acceptor:start_link/3 传入参数[self(),监听的端口,transport]
  4.         permanent,
  5.         2000,
  6.         worker,
  7.         [janus_acceptor]
  8.        },

看这个启动方法:

  1. start_link(Parent, Port, Module)
  2.   when is_pid(Parent),
  3.        is_list(Port),
  4.        is_atom(Module) ->
  5.     start_link(Parent, list_to_integer(Port), Module);

  6. start_link(Parent, Port, Module)
  7.   when is_pid(Parent),
  8.        is_integer(Port),
  9.        is_atom(Module) ->
  10.     Args = [Parent, Port, Module],
  11.     proc_lib:start_link(?MODULE, acceptor_init, Args).

调用了acceptor_init/3 函数:


 

  1. acceptor_init(Parent, Port, Module) ->
  2.     State = #state{
  3.       parent = Parent,
  4.       port = Port,
  5.       module = Module
  6.      },
  7.     error_logger:info_msg("Listening on port ~p~n", [Port]),
  8.     case (catch do_init(State)) of
  9.         {ok, ListenSocket} ->
  10.             proc_lib:init_ack(State#state.parent, {ok, self()}),
  11.             acceptor_loop(State#state{listener = ListenSocket});
  12.         Error ->
  13.             proc_lib:init_ack(Parent, Error),
  14.             error
  15.     end.


在do_init/1 中监听了端口,监听成功后再调用acceptor_loop/1 循环处理接入的客户端请求:


 

  1. acceptor_loop(State) ->
  2.     case (catch gen_tcp:accept(State#state.listener, 50000)) of
  3.         {ok, Socket} ->
  4.             handle_connection(State, Socket),
  5.             ?MODULE:acceptor_loop(State);
  6.         {error, Reason} ->
  7.             handle_error(Reason),
  8.             ?MODULE:acceptor_loop(State);
  9.         {'EXIT', Reason} ->
  10.             handle_error({'EXIT', Reason}),
  11.             ?MODULE:acceptor_loop(State)
  12.     end.


一旦有新的请求来到,则调用handle_connection 进行处理。

  1. handle_connection(State, Socket) ->
  2.     {ok, Pid} = janus_app:start_transport(State#state.port),
  3.     ok = gen_tcp:controlling_process(Socket, Pid),
  4.     %% Instruct the new handler to own the socket.
  5.     (State#state.module):set_socket(Pid, Socket).

janus_app:start_transport/1的定义:

  1. start_transport(Port) ->
  2.     supervisor:start_child(janus_transport_sup, [Port]).

这里的janus_transport_sup是主进程监控树下的一个子进程,而这个进程也是个监督树。start_child会在这个监督树下添加子进程。

----------------------------------------------------------------------------------------------暂停---------------------------------------------------------------------------------------------------------------

再来看janus_app.erl中定义的ID 为janus_transport_sup 这个子进程(类型为监督树)。

  1. %% Client instance supervisor
  2.        {janus_transport_sup,
  3.         {supervisor, start_link, [{local, janus_transport_sup},
  4.                                   ?MODULE, [Module]]},
  5.         permanent,
  6.         infinity,
  7.         supervisor,
  8.         []
  9.        }


supervisor:start_link/3  ,再看传入的参数:  [{local,janus_transport_sup},?MODULE,[Module]] ,第二个参数值为janus_app模块本身,第三个参数的值为transport 
所以就是再次以自己为模板使用transport为参数来初始化一个监控树,调用的为init/1:


 

  1. init([Module]) ->
  2.     {ok,
  3.      {_SupFlags = {simple_one_for_one, ?MAX_RESTART, ?MAX_TIME},
  4.       [
  5.        %% TCP Client
  6.        {undefined,
  7.         {Module, start_link, []},
  8.         temporary,
  9.         2000,
  10.         worker,
  11.         []
  12.        }
  13.       ]
  14.      }
  15.     }.

以上为这个supervisor的子进程的定义:一个transport的gen_server子进程。这个子进程的启动方法为start_link,


 

  1. start_link(Port)
  2.   when is_integer(Port) ->
  3.     gen_server:start_link(?MODULE, [Port], []).


---------------------------------------------------------------------------------------继续---------------------------------------------------------------------------------------------------------------------

目前的监控树结构如下:





刚才janus_sup 监听到了客户端的链接,并调用了janus_transport_sup:start_child/1

下面建立一个子进程,这个子进程启动时调用的是transport:start_link。所以服务器每接收到一个client 的连接请求就会在janus_transport_sup下添加相应的子进程。

再回到janus_acceptor:handle_connection/2,我们一但在janus_transport_sup下启动了子进程就会返回相应的PID。

然后调用gen_tcp:controlling_process/2  将这个客户端相应的socket 指定地绑定到此PID上:

  1. ok = gen_tcp:controlling_process(Socket, Pid),

下一句:

  1. (State#state.module):set_socket(Pid, Socket).


发送了一个异步的请求给刚才生成的进程,由transport:handle_cast 处理:

  1. handle_cast({set_socket, Socket}, State) ->
  2.     inet:setopts(Socket, [{active, once},
  3.                           {packet, 0},
  4.                           binary]),
  5.     {ok, Keep, Ref} = (State#state.transport):start(Socket),
  6.     keep_alive_or_close(Keep, State#state{socket = Socket, state = Ref});

这里需要注意(State#state.transport)这个record中的transport 值是在建立子进程时定义的:

  1. init([Port]) ->
  2.     process_flag(trap_exit, true),
  3.     {ok, #state{port = Port, transport = janus_flash }}.

所以(State#state.transport):start(Socket) 调用的是janus_flash:start/1:

  1. start(Socket) ->
  2.     Send = fun(Bin) -> gen_tcp:send(Socket, [Bin, 1]) end,
  3.     {ok, Proxy, Token} = client_proxy:start(Send),
  4.     State = #state{
  5.       socket = Socket,
  6.       proxy = Proxy,
  7.       token = Token
  8.      },
  9.     JSON = {struct,
  10.             [{<<"timestamp">>, tuple_to_list(now())},
  11.              {<<"token">>, Token}
  12.             ]},
  13.     send(mochijson2:encode(JSON), State).


这里调用client_proxy:start将 gen_tcp:send 函数包装成了一个gen_server 所以每一个transport 就附带了一个专门用来发送数据的gen_server。

在keep_alive_or_close里判断了当前连接是否需要关闭后整个客户端连接的初始化算是完成了。


TOPIC发布消息的处理:

每个socket 客户端连接都经过 gen_tcp:controlling_process 被绑定到一个gen_server 类型的进程:transport 。

所有发送到socket 的消息都由 transport:handle_info处理(验证)。


 

  1. handle_info(Info, State)
  2.   when State#state.transport /= undefined ->
  3.     Mod = State#state.transport,
  4.     {ok, Keep, TS} = Mod:process(Info, State#state.state),
  5.     keep_alive_or_close(Keep, State#state{state = TS});


以上代码中的Mod为janus_flash,所以具体的消息处理由janus_flash:process处理。

  1. process({ok, <<"PUBLISH">>, Rest}, State) ->
  2.     JSON = {struct, [{<<"topic">>, Topic},
  3.                      {<<"event">>, _},
  4.                      {<<"message_id">>, _},
  5.                      {<<"data">>, _}
  6.                     ]} = mochijson2:decode(Rest),
  7.     topman:publish(JSON, Topic),
  8.     {ok, shutdown, State};

将客户端发送过来的二进制数据解析为一个JSON 记录,然后交由topman:publish/2 来处理。

  1. publish(Msg, Topic)
  2.   when is_binary(Topic) ->
  3.     gen_server:abcast(?MODULE, {publish, Msg, Topic});


gen_server:abcast/2 会向本地指定的gen_server 发送一个请求,由相应gen_server 的handle_cast/2 函数进行处理。(gen_server:cast 和 gen_server:abcast区别)


 

  1. handle_cast({publish, Msg, Topic}, State) ->
  2.     {Srv, State1} = ensure_server(Topic, State),
  3.     pubsub:publish(Srv, Msg),
  4.     {noreply, State1};


这里先判断相应TOPIC的服务是否存在,不存在则新建一个。

 

原博客文章地址:

http://blog.csdn.net/yjl49/article/details/6934083








 

阅读(843) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~