Erlang で Apache worker MPM っぽいことを OTP っぽく実装してみた
たけまる / Erlang で Apache worker MPM っぽいこと
accept 待ちのプロセスを supervisor 配下に置いてみました。
tcp_server.hrl
-author('cooldaemon@gmail.com'). -define(MAX_TIME, 60). -define(SHUTDOWN_WAITING_TIME, 2000). -define(MAX_ACCEPT, 100).
tcp_server_sup.erl
-module(tcp_server_sup). -author('cooldaemon@gmail.com'). -behaviour(supervisor). -include("tcp_server.hrl"). % External API -export([start_link/3, stop/0]). % Callbacks -export([init/1]). % External API start_link(Port, MaxConnection, Callback) -> supervisor:start_link( {local, ?MODULE}, ?MODULE, [Port, MaxConnection, Callback] ). stop() -> case whereis(?MODULE) of Pid when pid(Pid) -> exit(Pid, shutdown), ok; _ -> not_started end. % Callbacks init([Port, MaxConnection, Callback]) -> case gen_tcp:listen( Port, [{active, false}, binary, {packet, line}, {reuseaddr, true}] ) of {ok, ListenSocket} -> init_result(MaxConnection, Callback, ListenSocket); {error, Reason} -> {stop, Reason} end. % Internal Functions init_result(MaxConnection, Callback, ListenSocket) -> {ok, {{one_for_one, ?MAX_RESTART, ?MAX_TIME}, lists:map( fun (X) -> { list_to_atom("tcp_acceptor_" ++ integer_to_list(X)), {tcp_acceptor, start_link, [Callback, ListenSocket]}, permanent, ?SHUTDOWN_WAITING_TIME, worker, [tcp_acceptor] } end, lists:seq(1, MaxConnection) ) }}.
tcp_acceptor.erl
-module(tcp_acceptor). -author('cooldaemon@gmail.com'). % External API -export([start_link/2]). % Callbacks -export([init/3, accept/2]). % External API start_link(Callback, ListenSocket) -> proc_lib:start_link(?MODULE, init, [self(), Callback, ListenSocket]). % Callbacks init(Parent, Callback, ListenSocket) -> proc_lib:init_ack(Parent, {ok, self()}), accept(Callback, ListenSocket). accept(Callback, ListenSocket) -> {ok, Socket} = gen_tcp:accept(ListenSocket), case Callback of {Module, Function} -> Module:Function(Socket); _ -> Callback(Socket) end, accept(Callback, ListenSocket).
echo.erl
-module(echo). -author('cooldaemon@gmail.com'). % Callbacks -export([main/1]). main(Socket) -> case gen_tcp:recv(Socket, 0) of {ok, <<"bye\r\n">>} -> gen_tcp:send(Socket, <<"cya\r\n">>), gen_tcp:close(Socket); {ok, Data} -> gen_tcp:send(Socket, Data), main(Socket); {error, closed} -> ok; {error, Reason} -> exit({gen_tcp_recv_error, Reason}) end.
echo.erl を差し替える事で、用途に応じた処理が可能です。
gen_tcp:listen/2 のオプションを設定できるようにしたり、現在の状態(ポート番号だとか、accept 済みのプロセス数だとか)を保持する gen_server を加えたりした方が、より実用的になる気がしますけど、とりあえず、これくらいで OTP っぽくなってるかなぁと。
使い方は、下記の通り。
$ erl 1> tcp_server_sup:start_link(11211, 2, {echo, main}).
例のごとく、添削熱烈歓迎。