Erlangをまだコソコソいじっていたりするのですが:
プロセス間通信を利用する際に、RPC(Remote Procedure Call)みたいな形にすると使いやすいこともあるようです。RPCとは言っても、実際にはリモート(遠隔地)じゃなくて近距離だったりするのですが、、、こういうの、なんと呼べばいいのかな? ローカルRPC -- ひどく語義矛盾してるが、前例があるからこの言葉(LRPCと略記)を使おう、っと
LRPCサーバー
LRPCのサーバー部分はこんな感じでしょう。
start_server() ->
spawn(?MODULE, server_main, []).server_main() ->
receive
{call, ClientPid, FunctionId, Args} -> % 呼び出しリクエスト
case exec(FunctionId, Args) of
{error, Reason} -> ?RETURN(ClientPid, {return, error, Reason});
{ok, Value} -> ?RETURN(ClientPid, {return, ok, Value})
end;
stop -> % プロセス終了命令
io:fwrite("~w: bye bye.\n", [self()]),
exit(ok);
_Other -> io:fwrite("Oops!\n")
end,
server_main(). % 無限ループ
{call, ClientPid, FunctionId, Args}
が、LRPCリクエストの“パケット”ってことになります。exec(FunctionId, Args)
がサーバー側手順を実行しますが、例として、足し算(sum)と大きな方*1を選ぶ関数(max)。
exec(sum, Args) ->
{ok, lists:sum(Args)};
exec(max, Args) ->
{ok, lists:max(Args)};
exec(_, _) ->
{error, "unknown function."}.
これはえらく安直だけど、どこの誰からLRPCリクエストが来るかわからないときは、引数チェックをきびしくしないとまずいでしょうね。
?RETURN
はマクロ呼び出しで、デバッグの都合を考慮して次のように定義しています。
-ifdef(DEBUG).
-define(RETURN(ClientPid, X),
io:fwrite("To: ~w, Returns:~w\n", [ClientPid, X])).
-else.
-define(RETURN(ClientPid, X),
ClientPid!X).
-endif.
-define(DEBUG, true).
としてコンパイルすれば*2、次のようにしてサーバー動作を確認できます。
Erlang (BEAM) emulator version 5.5.4 [async-threads:0]Eshell V5.5.4 (abort with ^G)
1> c(lrpc).
{ok,lrpc}
2> P = lrpc:start_server().<0.1809.0>
3> P!{call, self(), sum, [2, 3]}.
To: <0.1802.0>, Returns:{return,ok,5}
{call,<0.1802.0>,sum,[2,3]}
4> P!{call, self(), max, [2, 3]}.
To: <0.1802.0>, Returns:{return,ok,3}
{call,<0.1802.0>,max,[2,3]}
5>
ただし、文字列が数値リストで表示されたりします(Erlangの文字列とは、実は数値リストですから)。
この数値リストはエラーメッセージなのですけどね(苦笑)。
5> P!{call, self(), foo, [2, 3]}.
To: <0.1802.0>, Returns:{return,error,[117,110,107,110,111,119,110,32,102,117,11
0,99,116,105,111,110,46]}
{call,<0.1802.0>,foo,[2,3]}
6>
6> io:fwrite("~s\n", [[117,110,107,110,111,119,110,32,102,117,110,99,116,105,111
,110,46]]).
unknown function.
ok
7>
LRPCクライアント
LRPCのクライアント側はこんな感じ。
call(ServerPid, FunctionId, Args) ->
ServerPid! {call, self(), FunctionId, Args},
receive
{return, ok, Value} -> {ok, Value};
{return, error, Reason} -> {error, Reason}
after 2*1000 -> {error, "timeout."}
end.
sum(ServerPid, N, M) -> call(ServerPid, sum, [N, M]).
max(ServerPid, N, M) -> call(ServerPid, max, [N, M]).
-define(DEBUG, true).
は外してコンパイルし直して実行してみます。
7> c(lrpc).
{ok,lrpc}
8> f().
ok
9> P = lrpc:start_server().<0.1832.0>
10> lrpc:sum(P, 2, 3).
{ok,5}
11> lrpc:max(P, 2, 3).
{ok,3}
12>