gen_udp を Supervisor 配下に置いて UDP Server 作ってみた
Kademlia の erlang 実装で使おうと思って書いてみた。
いろいろ修正した。詳細は下記。
-module(udp_server). -author('cooldaemon@gmail.com'). -behaviour(supervisor). -record(udp_server_option, { option = [binary], port = 4000, max_restarts = 3, time = 60, shutdown = 2000, recv_length = 0, recv_timeout = infinity }). -export([behaviour_info/1]). -export([start_link/1, start_link/2, start_link/3]). -export([stop/0, stop/1]). -export([init/1]). -export([receiver_start_link/3]). -export([receiver_init/3]). % Behaviour Callbacks behaviour_info(callbacks) -> [{handle_call, 4}, {handle_info, 1}]; behaviour_info(_Other) -> undefined. % Supervisor %% External APIs start_link(Module) -> start_link(Module, #udp_server_option{}). start_link(RegistName, Module) when is_atom(Module) -> start_link(RegistName, Module, #udp_server_option{}); start_link(Module, Option) -> start_link({local, ?MODULE}, Module, Option). start_link({Dest, Name}=RegistName, Module, Option) -> supervisor:start_link( {Dest, supervisor_name(Name)}, ?MODULE, [RegistName, Module, Option] ). stop() -> stop(?MODULE). stop(Name) -> case whereis(supervisor_name(Name)) of Pid when is_pid(Pid) -> exit(Pid, normal), ok; _ -> not_started end. supervisor_name(Name) -> list_to_atom(atom_to_list(Name) ++ "_sup"). %% Callbacks init([{_Dest, Name}=RegistName, Module, Option]) -> #udp_server_option{ max_restarts = MaxRestarts, time = Time, shutdown = Shutdown } = Option, {ok, {{one_for_one, MaxRestarts, Time}, [ { Name, {?MODULE, receiver_start_link, [RegistName, Module, Option]}, permanent, Shutdown, worker, [] } ]}}. % ProcLib - udp_server_receiver %% External APIs receiver_start_link({Dest, Name}, Module, Option) -> {ok, Pid} = proc_lib:start_link(?MODULE, receiver_init, [self(), Module, Option]), case Dest of local -> register(Name, Pid); _Global -> global:register_name(Name, Pid) end, {ok, Pid}. %% Callbacks receiver_init(Parent, Module, Option) -> case gen_udp:open( Option#udp_server_option.port, Option#udp_server_option.option ) of {ok, Socket} -> proc_lib:init_ack(Parent, {ok, self()}), recv( proplists:get_value(active, Option#udp_server_option.option), Socket, Module, Option ); {error, Reason} -> exit({error, Reason}) end. recv(false, Socket, Module, Option) -> case gen_udp:recv( Socket, Option#udp_server_option.recv_length, Option#udp_server_option.recv_timeout ) of {ok, {Address, Port, Packet}} -> Module:handle_call(Socket, Address, Port, Packet), recv(false, Socket, Module, Option); {error, Reason} -> exit({error, Reason}) end; recv(_Active, Socket, Module, Option) -> receive {udp, Socket, Address, Port, Packet} -> Module:handle_call(Socket, Address, Port, Packet), recv(true, Socket, Module, Option); OtherMessage -> Module:handle_info(OtherMessage), recv(true, Socket, Module, Option) after Option#udp_server_option.recv_timeout -> exit({error, udp_timeout}) end.
初めは、receive する度に start_child を使って Supervisor 配下に子プロセスを追加する仕組みにしていたが、別に子プロセスが死のうと生きようと関係ないやーと思い直して spawn にしてみた。
spawn する・しないは、呼び先に任せてるべきだと思ったので、spawn する事を止めた。
んー、active モード時の為に他のメッセージも受け付けるようにしてみたけど、使い道が無かった。
メッセージを送りやすいよう、receiver に名前を付けた。
global で名前を付けると、stop は動かない。
udp_server を使った echo server は下記の通り。
-module(udp_echo). -author('cooldaemon@gmail.com'). -behaviour(udp_server). -export([start/0, stop/0]). -export([handle_call/4, handle_info/1]). -export([test/0]). % External APIs start() -> udp_server:start_link(?MODULE). stop() -> udp_server:stop(). % Callbacks handle_call(Socket, Address, Port, Packet) -> io:fwrite("receive:~p~n", [binary_to_term(Packet)]), gen_udp:send(Socket, Address, Port, Packet), ok. handle_info(_Message) -> ok. % Test test() -> {ok, Socket} = gen_udp:open(0, [binary]), gen_udp:send(Socket, "localhost", 4000, term_to_binary({foo, bar})), gen_udp:close(Socket).
突っ込み、添削、大歓迎。
tcp_server とペアで広まるとよさそう.ただ,TCP は accept して子ソケットが作られるので処理が複雑になるけど (だから tcp_server が嬉しいけど),UDP は単純なので普通に書いてもいいのかも
tcp_server の EDOC 書きとか、Bug Fix が終わってないのに浮気してて、ごめんなさい orz
えーっと、ポートを開けて待っているプロセスは、落ちられると困るので Supervisor 配下が良いかと思います。
こうしておくと、udp_server の実装側で例外発生時にバシバシ落とせて、Erlang っぽいかなーと(w;
後は、init を呼んだり、State を持ち回るようにしたら、更にそれっぽいかも。