2007-10-24
supervisor の simple_one_for_one を使って echoserver を書き直した
gen_tcp の練習で書いた id:cooldaemon:20070614:1181827975 を gen_server 化しようと考えたが、gen_tcp モジュールの accept や recv は処理をブロックするので、gen_server 化を諦めて id:cooldaemon:20070717:1184676090 のように proc_lib を使ってお茶を濁していた。
しかし、つい最近、Erlang Community - Building a Non-blocking TCP server using OTP principles - Trapexit を読んで、prim_inet モジュールを使う手がある事を知る。
残念な事に prim_inet は、直接使う事が推奨されていない為、仕事で erlang を使いたい私は、全く使う気になれなかったが、simple_one_for_one の使い方等が非常に参考になったので、echoserver を書き直してみる事にした。
Server Design
+----------------+
| tcp_server_sup |
+--------+-------+
| (one_for_one)
+----------------+---------+
| |
+-------+------+ +-------+--------+
| tcp_acceptor | + tcp_client_sup |
+--------------+ +-------+--------+
| (simple_one_for_one)
+-----|---------+
+-------|--------+|
+--------+-------+|+
| tcp_echo |+
+----------------+
ほぼ、Erlang Community - Building a Non-blocking TCP server using OTP principles - Trapexit のパクリ。
Srouce
tcp_server.hrl
-author('cooldaemon@gmail.com'). -define(MAX_RESTART, 5). -define(MAX_TIME, 60). -define(SHUTDOWN_WAITING_TIME, 2000).
各 supervisor の init の戻り値に使う定数を宣言。
tcp_server_sup.erl
-module(tcp_server_sup). -author('cooldaemon@gmail.com'). -behaviour(supervisor). -include("tcp_server.hrl"). % External API -export([start_link/2, stop/0]). % Callbacks -export([init/1]). % External API start_link(Port, Module) -> supervisor:start_link({local, ?MODULE}, ?MODULE, [Port, Module]). stop() -> case whereis(?MODULE) of Pid when pid(Pid) -> exit(Pid, shutdown), ok; _ -> not_started end. % Callbacks init([Port, Module]) -> {ok, {{one_for_one, ?MAX_RESTART, ?MAX_TIME}, [ { tcp_acceptor, {tcp_acceptor, start_link, [Port]}, permanent, ?SHUTDOWN_WAITING_TIME, worker, [tcp_acceptor] }, { tcp_client_sup, {tcp_client_sup, start_link, [Module]}, permanent, infinity, supervisor, [] } ]}}.
start_link を実行すると tcp_acceptor と tcp_client_sup を起動する。
start_link の引数 Port は listen するポート番号。Module は、accept した Socket を処理するモジュールの名前。
今回、Module には echoserver として機能する tcp_echo を指定するが、用途によって切り替える事ができる。
tcp_acceptor.erl
-module(tcp_acceptor). -author('cooldaemon@gmail.com'). % External API -export([start_link/1]). % Callbacks -export([init/2, accept/1]). % External API start_link(Port) -> proc_lib:start_link(?MODULE, init, [self(), Port]). % Callbacks init(Parent, Port) -> case gen_tcp:listen( Port, [{active, false}, binary, {packet, line}, {reuseaddr, true}] ) of {ok, ListenSocket} -> proc_lib:init_ack(Parent, {ok, self()}), accept(ListenSocket); {error, Reason} -> proc_lib:init_ack(Parent, {error, Reason}), error end. accept(ListenSocket) -> {ok, Socket} = gen_tcp:accept(ListenSocket), tcp_client_sup:start_child(Socket), accept(ListenSocket).
listen と accept を担当。
accept した Socket は tcp_client_sup:start_child 経由で tcp_echo に渡す。
tcp_client_sup.erl
-module(tcp_client_sup). -aauthor('cooldaemon@gmail.com'). -behaviour(supervisor). -include("tcp_server.hrl"). % External API -export([start_link/1, start_child/1]). % Callbacks -export([init/1]). % External API start_link(Module) -> supervisor:start_link({local, ?MODULE}, ?MODULE, [Module]). start_child(Socket) -> supervisor:start_child(?MODULE, [Socket]). % Callbacks init([Module]) -> {ok, {{simple_one_for_one, ?MAX_RESTART, ?MAX_TIME}, [{ undefined, {Module, start_link, []}, temporary, ?SHUTDOWN_WAITING_TIME, worker, [] }]}}.
simple_one_for_one を使っている為、Module:start_link (今回は、tcp_echo:start_link) は init 終了時に実行されず、start_child 実行時に実行される。
ちなみに、erlang 付属の httpd モジュールでは、似たような処理を one_for_one で行っている。
tcp_echo.erl
-module(tcp_echo). -author('cooldaemon@gmail.com'). % External API -export([start_link/1]). % Callbacks -export([init/2, recv/1]). % External API start_link(Socket) -> proc_lib:start_link(?MODULE, init, [self(), Socket]). % Callbacks init(Parent, Socket) -> proc_lib:init_ack(Parent, {ok, self()}), recv(Socket). recv(Socket) -> case gen_tcp:recv(Socket, 0) of {ok, B} -> case B of <<"bye\r\n">> -> gen_tcp:send(Socket, <<"cya\r\n">>), gen_tcp:close(Socket); Other -> gen_tcp:send(Socket, Other), recv(Socket) end; {error, closed} -> ok end.
渡された Socket を使って recv したり send したり・・・。
Running
% erl
1> tcp_server_sup:start_link(11211, tcp_echo).
{ok,<0.35.0>}
% telnet 127.0.0.1 11211 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. test test hogehoge hogehoge bye cya Connection closed by foreign host.
2> tcp_server_sup:stop(). ok ** exited: shutdown **
添削大歓迎
- 13 http://www.google.co.jp/search?sourceid=navclient&hl=ja&ie=UTF-8&rlz=1T4ADBR_jaJP231JP232&q=if 式
- 13 http://www.google.com/search?hl=ja&lr=lang_ja&ie=UTF-8&oe=UTF-8&q=Vim7+set+guifont&num=50
- 8 http://d.hatena.ne.jp/keyworddiary/erlang
- 8 http://www.google.co.jp/search?hl=ja&client=firefox-a&rls=org.mozilla:ja:official&hs=gSx&q=trac+ソースコード&btnG=検索&lr=lang_ja
- 7 http://www.google.co.jp/search?hl=ja&client=firefox&rls=org.mozilla:ja:official&hs=29Y&q=セッション サンプル&btnG=検索&lr=lang_ja
- 6 http://en.yummy.stripper.jp/?eid=374180
- 4 http://43hr.org/2007/06/18/67/
- 3 http://72.14.235.104/search?q=cache:GNSjFCLRcUkJ:d.hatena.ne.jp/cooldaemon/20070720/1184903937+macport+svn&hl=ja&ct=clnk&cd=2&gl=jp&client=firefox-a
- 3 http://memo.xight.org/2007-02-15-1
- 3 http://reader.livedoor.com/reader/