LoginSignup
8

More than 5 years have passed since last update.

posted at

updated at

Erlangのノード間通信をRubyで実装してみる

Erlangのノード間通信を理解するために,Erlangの分散プロトコルを実装してErlangノードと通信してみました.(Erlang Advent Calendar用のネタを思いつかなかったので大分前に職場で話した内容です...)

やりたかったこと:

  • Erlang Distribution Protocolについての理解を深める
  • Rubyから gen_server:call/2rpc:call/4 してみる
  • Erlang + Ruby + Sinatraで何か作る

Erlangのノード間通信に必要なもの

Erlangノードに接続してメッセージを送受信するためには以下のものが必要です.

  • EPMD (Erlang Port Mapper Daemon) : DNS的なやつ
  • Erlang Distribution Protocol : ハンドシェイクとメッセージ送受信のプロトコル
  • External Term Format : メッセージのシリアライズの仕様

EPMD(Erlang Port Mapper Daemon)

あるホストで起動しているErlangノードがlistenしているポート番号を管理するデーモンです.
Erlangを分散モードで起動すると,自動的に起動して4369番ポートで待ち受けています.

プロトコルの仕様はErlangのドキュメントのDistribution Protocol に書かれています.

TCPで接続してリクエストを送るとレスポンスが返ってくる単純な通信です.認証なども不要で簡単にリクエストを送れます.

  • リクエストのメッセージフォーマット: リクエスト長(16bit) + リクエスト
  • リクエストの1バイト目がリクエストの種別になっている
種別 名前 説明
120 ALIVE2_REQ ノード登録
110 NAMES_REQ ノード一覧取得
115 STOP_REQ ノード抹消(切断時に自動削除されるので通常不要)

(レスポンスは省略)

リクエストの種別の値のルールは良くわかりませんが,おそらくASCIIコードです.

  • NAMES の 'n' (110)
  • STOP の 's' (115)
  • ALIVE2 が 'x' (120) なのは謎です(単に'a'が埋まってたから?)

ノード一覧取得を取得してみる

1バイトのメッセージ 110(NAMES_REQ) を送ればノードの一覧を取得できます.

適当にErlangを分散モードで起動した状態で,試しにシェルからnetcatで問い合わせてみます.

$ echo -ne "\0x01\x01\x6e" | nc localhost 4369
 name node1 at port 46337

node1 が port 46337で起動していることが分わかりました.

Erlang Distoribution Protocol

ノード間通信を始めるためのハンドシェイクの方法と,プロセス間のメッセージの送受信の方法を定めているプロトコルです.link/monitorの方法も定めれています.

プロトコルの仕様はErlangのドキュメントのDistribution Protocol に書かれていますが,足りない情報もあるので https://github.com/erlang/otp/ * /internal_doc あたりにあるドキュメントやErlangのソースを読む必要がありました.

ハンドシェイク

cookieでの認証もこのときに行われます(cookieが違うとハンドシェイクに失敗するのでメッセージを送信できない).

  • フォーマット: データ長(16bit) + タイプ(8bit) + 本体
  • 認証はチャレンジ&レスポンス
  • ハッシュ関数はMD5

image

メッセージの送受信

  • メッセージ長(32bit) + メッセージ
  • 2種類の形式があるが,出来ることは同じ
4 Bytes d Bytes n Bytes m Bytes
Length = d+n+m DistributionHeader ControlMessage Message
4 Bytes 1 Bytes n Bytes m Bytes
Length = 1+n+m Type ('p') ControlMessage Message
  • 前者(DistributionHeader あり)の方は少し複雑だがatomをキャッシュできるためサイズを抑えられる
  • ControlMessage と Message は External Binary Format(※後述)形式
  • ControlMessage はメッセージの種類と宛先プロセスなどの情報が入っています
  • Message はErlangプロセスに送信したいメッセージ

rpc:callを呼ぶ場合以下のようなメッセージが送られます

  • ControlMessage = {6, FromPid, '', rex} %% 6 : REG_SEND
  • Message = {'$gen_call', {FromPid,Tag}, {call, Mod, Fun, Args, user}}

rpcは rex という名前のgen_serverが処理するので,そのプロセスにメッセージを送っています.

External Binary Format

term_to_binary/1, binary_to_term/1 でお馴染みのバイナリフォーマットです.

ドキュメントのExternal Term Formatを読めば良いだけですが,型ごとにデータ長の持ち方等も違うので実装する場合は少し面倒くさいです.

1つの型に複数の表現方法が存在する場合がありますが,互換性のために用意されているもので通信相手がErlangならハンドシェイク時のversionとflagsで有効・無効を切り替えられるため,全てに対応する必要はありません.

Ruby 実装

雑な実装であまり実用的ではないですが,サンプルとしてSinatraでWebサーバ立ててブラウザからErlangのノードの情報を見たり,プロセスのlink/monitorの構造をWebGLで表示できるようにしてみました.

Erlangの関数を呼び出してみる

まずErlangを起動します.

erl -sname node1 -setcookie testtest

irbから上記リポジトリの erlang.rb をrequireして呼び出します.

irb
irb(main):001:0> require_relative 'erlang'
=> true
irb(main):002:0> erl = Erlang::Erl.new('rubynode@test', "testtest")
=> ...
irb(main):003:0> erl.nodes
=> ["node1"]
irb(main):004:0> erl.rpc_call(erl.nodes[0], :io, :format, ["Hello!\n"])
=> :ok
irb(main):005:0> erl.eval(erl.nodes[0], "1 + 1.")
=> 2

リモートノードでErlangのコードをevalできるのは便利です.個人的に欲しかったので実装しました.

対応した型

基本的な型は一通り対応してみました.mapsとかもRubyのHashと相互変換されます.

  TYPE_NEW_FLOAT = 70
  TYPE_SMALL_INT = 97
  TYPE_INTEGER   = 98
  TYPE_FLOAT     = 99
  TYPE_ATOM      = 100
  TYPE_PORT      = 102
  TYPE_PID       = 103
  TYPE_SMALL_TUPLE = 104
  TYPE_LARGE_TUPLE = 105
  TYPE_NIL       = 106
  TYPE_STRING    = 107
  TYPE_LIST      = 108
  TYPE_BINARY    = 109
  TYPE_SMALL_BIG = 110
  TYPE_LARGE_BIG = 111
  TYPE_FUN       = 112
  TYPE_NEW_REF   = 114
  TYPE_MAP       = 116

WebGLでプロセスを表示してみる

この記事の趣旨とはあまり関係が無い上に何かの役に立つわけでもないものですが,せっかくRubyとErlang間で色々出来るので簡単なWebアプリを作ってみました(普通にErlangで書けば良いだろうと突っ込まれそうですが)

procvis.png

表示サンプル(リンク先は静的ページなのでプロセス殺したりは出来ません):

色々なErlangアプリケーションを起動してプロセスのlinkの関係を見ているだけでもけっこう面白いです.

参考にしたもの

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
8