このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

ErlangとJavaのあいだでリモートメッセージング (後編)

「ErlangとJavaのあいだでリモートメッセージング」の後編です。JInterfaceライブラリを使った、ErlangノードとJavaプログラムのあいだのメッセージ通信を紹介します。ちょっと急ぎ足ですが、Java側のセットアップとプログラミングを概観します。

内容:

  1. まずは実際に試してみよう:JavaプログラムからHello送信
  2. ErlangでもJavaでもほぼ同じ
  3. ErlangノードとJavaノード
  4. 最後に

参考資料:

※ 単に「プロセス」と言った場合は、Erlang処理系内部で走る軽量プロセスのことです。OSが管理するプロセスは「OSプロセス」と呼ぶことにします。

●まずは実際に試してみよう:JavaプログラムからHello送信

前編では、「ErlangErlang」のメッセージ通信を説明しました。「ErlangErlang」通信を理解していれば、「ErlangJava」通信も同じ原理なので、難しくはありません。

ではさっそく実験をしましょう。前回も使った次のプログラムを例にします。


%% receiver.erl
-module(receiver).
-compile(export_all).

start() ->
spawn(?MODULE, receiver_main, []).

receiver_main() ->
receive
stop -> % プロセス終了
io:fwrite("~w:bye bye.\n", [self()]),
exit(ok);
{message, Pid, Message} -> % データ受信
io:fwrite("Received! ===> From:~w Message:~s\n", [Pid, Message]);
Other -> % それ以外
io:fwrite("Oops! ~w is ignored.\n", [Other])
end,
receiver_main().

名前(ショートネーム)がfirst@TP-X31-HIYAMA-2であるノードにおいて、上のプログラムがstartしており、生成されたプロセスにはrcvという名前が付いているとします。実際にこの状況にするには次の手順を踏みます(receiver.erlはコンパイル済みと仮定する)。


werl -sname first

Erlang (BEAM) emulator version 5.5.4 [async-threads:0]

Eshell V5.5.4 (abort with ^G)
(first@TP-X31-HIYAMA-2)1> l(receiver).
{module,receiver}
(first@TP-X31-HIYAMA-2)2> register(rcv, receiver:start()).
true
(first@TP-X31-HIYAMA-2)3>

今度はJava側の準備をしましょう。通常のJava開発環境(javacが動けばいい)はあるものとします。$ERLANG_HOMEはErlangがインストールされたディレクトリだとして、$ERLANG_HOME/lib/jinterface-1.3/priv/OtpErlang.jar というjarファイルがあります。このjarファイルに「ErlangJava」通信をサポートするJInterfaceが含まれます。OtpErlang.jarがCLASSPATHに入るように設定してください。

とりあえず(説明は後回し)、次のJavaプログラムをコンパイルします。ただし、定数REMOTE_NODEのノード名は各自の環境にあうように書き換えてください。TP-X31-HIYAMA-2は、僕(檜山)のPCのホスト名ですからね。


// HelloSender.java
import com.ericsson.otp.erlang.*;

public class HelloSender {

// REMOTE_NODEは各自の環境にあわせて書き換える
static final String REMOTE_NODE = "first@TP-X31-HIYAMA-2";

public static void main(String[] args) {
OtpNode thisNode = null;
try {
thisNode = new OtpNode("java");
} catch (Exception e) {
System.err.println(e);
System.exit(1);
}
OtpMbox mbox = thisNode.createMbox("main");

System.out.println("Sending message \"Hello\" to {rcv, '" +
REMOTE_NODE +
"'}.");

if (thisNode.ping(REMOTE_NODE, 2000)) {
System.out.print("Remote node is up, ");
} else {
System.out.println("Remote node is not up.");
System.exit(1);
}
// remote node is up.
OtpErlangObject[] msgPacket = new OtpErlangObject[3];
msgPacket[0] = new OtpErlangAtom("message");
msgPacket[1] = mbox.self();
msgPacket[2] = new OtpErlangString("Hello");
OtpErlangTuple msgPacketTuple = new OtpErlangTuple(msgPacket);
mbox.send("rcv", REMOTE_NODE, msgPacketTuple);
System.out.println("sent.");
}
}

javac HelloSender.javaのようにしてコンパイルしたら実行してみましょう。


>java -cp .;%CLASSPATH% HelloSender
Sending message "Hello" to {rcv, 'first@TP-X31-HIYAMA-2'}.
Remote node is up, sent.

>

そうすると、Erlangノードfirst@TP-X31-HIYAMA-2では:


Received! ===> From:<5168.1.0> Message:Hello
(first@TP-X31-HIYAMA-2)3>

ちゃんとメッセージを受け取っています。とりあえずメデタシ。

ErlangでもJavaでもほぼ同じ

先に出したJavaプログラムでは、メッセージ送信はしますが受信はしません。そこで、以下のように受信部分を付け足しておきましょう。


// HelloSender.java
import com.ericsson.otp.erlang.*;

public class HelloSender {
… 前のプログラムコードとまったく同じ …
public static void main(String[] args) {
… 前のプログラムコードとまったく同じ …

System.out.print("Waiting for a message ...");
OtpErlangObject msg;
try {
msg = mbox.receive();
} catch (Exception e) {
System.out.println(" error! abort.");
System.exit(1);
}
// メッセージ内容はチェックしない
System.out.println(" received! bye bye.");
System.exit(0);
}
}

この(追加部分も含めた)Javaプログラムは、以下のErlangプログラムとほぼ同じ動作をします。


% hello_sender.erl
-module(hello_sender).
-compile(export_all).

% REMOTE_NODEは各自の環境にあわせて書き換える
-define(REMOTE_NODE, 'first@TP-X31-HIYAMA-2').

start() ->
spawn(?MODULE, hello_sender_main, []).

hello_sender_main() ->
io:fwrite("Sending message \"Hello\" to {rcv, ~w}.\n",
[?REMOTE_NODE]),
case net_adm:ping(?REMOTE_NODE) of
pong ->
io:fwrite("Remote node is up, ");
bang ->
io:fwrite("Remote node is not up.\n"),
exit(ng)
end,
{rcv, ?REMOTE_NODE} ! {message, self(), "Hello"},
io:fwrite("sent.\n"),

io:fwrite("Waiting for a message ..."),
receive
_Any -> % メッセージ内容はチェックしない
io:fwrite(" received! bye bye.\n"),
exit(ok)
end.

例えば、次の2つの状況を、メッセージを受け取るfirst@TP-X31-HIYAMA-2からは区別できないはずです。

  1. werl -sname secondとして立ち上げた別なErlangノードsecond@TP-X31-HIYAMA-2上でhello_sender:start().した場合。
  2. HelloSenderをJavaJVM)上で実行した場合。

どちらも、Erlangのメッセージ・データ {message, SenderPid, "Hello"} がfirst@TP-X31-HIYAMA-2に向けて送られます。

しかし実際は、挙動が少し違うんですよねぇ(苦笑)。Erlangシェルからpid(5168, 1, 0) ! ack.のようにしても、Java側に届かないようです、Erlangノードになら、PIDでちゃんと届くのですけど。PIDではなくて、名前を使って {main, 'java@TP-X31-HIYAMA-2'} ! ack.ならOKです。PIDがいつでもダメかというと、そうではなくて、シェルからの手動はダメですがプログラムからならPIDが使えます。そのことは、receiver.erlを次のように手直しして確認できます。


… 前のプログラムコードとまったく同じ …
receive
stop -> % プロセス終了
io:fwrite("~w:bye bye.\n", [self()]),
exit(ok);
{message, Pid, Message} -> % データ受信
io:fwrite("Received! ===> From:~w Message:~s\n", [Pid, Message]),
timer:sleep(2000), % 少し待ってから
Pid ! ack; % 送信元に返答する

Other -> % それ以外
io:fwrite("Oops! ~w is ignored.\n", [Other])
end,
うーん、なんだかなぁー? 現状、事情がわかりません。が、まー、細かいこと言わなければErlangでもJavaでも同じように動作するわけです。

ErlangノードとJavaノード

本物のErlangノードとJavaノード(Javaで書かれた擬似的なノード)は、ほぼ同じ構造を持ちます。次のような対応関係があると思えばいいでしょう。

Erlangノード Javaノード
起動コマンド erl -sname name ノード生成 new OtpNode("name")
プロセス生成 spawn/3 メールボックス生成 node.createMbox()
名前付け register(name, Proc) 名前付け mbox.registerName("name")
確認 net_adm:ping(RemoteNode) 確認 node.ping(remoteNode, timeout)
送信 {Name, RemoteNode} ! Msg 送信 mbox.send(name, remoteNode, msg)
受信 receive ... end 受信 mbox.receive()

多少違和感があるのが、プロセスとメールボックス(メッセージキュー)の対応関係でしょう、補足します; Erlangでは、プロセスにメールボックスが作り付けになっています。メッセージを送受信する主体はプロセスなので、メールボックスそれ自体は通常は意識されません。一方、Javaにはプロセス概念がありません。スレッドを使ってErlangプロセスを模倣<もほう>することはできるかもしれませんが、単にErlangノードと通信するために、プロセスまで模倣する必要はありません。そこで、プロセスに内在しているメッセージ送受信機構であるメールボックスだけを取り出してJavaクラスにしたのがOtpMboxなのです。

●最後に

他にも触れたい話題があるのですが、前後2回の予定だったのでこのくらいにしておきます。言い残したことは、別なエントリーとして、おいおい取り上げるかもしれません。

最後に僕の感想を少し; Erlangは、並列プログラミングや分散プログラミングのための非常に強力なツール/環境ですが、不足/不便な点があることも事実です。そのような不足/不便をJavaで補いたいならJInterfaceがよい手段を提供するでしょう。

JInterfaceはErlangネイティブのプロトコルを直接的に使用しますが、HTTP上のJSON-RPCなどを介してErlangプロセスにアクセスする方法もあります。Erlangプロトコルは、TCP/IPのポートもコネクションもそれなりに消費するので、インターネット上での運用は難しいでしょう。現実的には、ノード間直接通信はバックヤードのLAN内に限定されそうですが、それでも利用価値は十分あると思います。