dRubyでの参照渡しの続きです。dRubyでの参照渡しは、厳密に言うと参照オブジェクトの値渡しであることは前回学びました。参照オブジェクトとはDRbObjectのことなので、まずDRbObjectの内容を見ていくようにしましょう。
DRbObjectには二つのインスタンス変数があります。
- @ref
- オブジェクトの実体のID(hoge.__id__したものです。)
- @url
- dRubyサーバーを特定するURI
DRbObjectをnewするときに何かオブジェクトを渡すと、それが@refに入ります。例えばirbでこんなことをやってみます。
[サーバー側]
irb(main):001:0> require 'drb/drb'
=> true
irb(main):002:0> uri = 'druby://サーバーのホスト名:9898'
=> "druby://サーバーのホスト名:9898"
irb(main):003:0> h = {}
=> {}
irb(main):004:0> DRb.start_service(uri, h)
=> #<DRb::DRbServer:0x295ecd0 ・・・
irb(main):005:0> str = 'foo'
=> "foo"
irb(main):006:0> str.__id__
=> 21680810
irb(main):007:0> ref = DRbObject.new(str)
=> #<DRb::DRbObject:0x29530b0 @ref=21680810, @uri="druby://サーバーのホスト名:9898">
irb(main):008:0> h[1] = ref
=> #<DRb::DRbObject:0x29530b0 @ref=21680810, @uri="druby://サーバーのホスト名:9898">
最後から2つ目の行(:007)を見ます。ここで作成したDRbObjectのインスタンスの内容を確認します。@refは、引数に渡されたstrというオブジェクトのIDが設定されています。@uriは、start_service時に指定したuriが入っています。DRbObjectのインスタンスを作ると、dRubyサービスのURIとオブジェクトが自動的に紐付きます。
なお、前回のエントリで確認したように、DRbObjectのインスタンスを作る前にstart_serviceしてないとdRubyサービスが無いのでエラーになります。ただ、すっかりおなじみのnew_with_uriだけは特殊で、指定したURIのフロントオブジェクトに対する参照をもらうだけなのでstart_serviceしてなくても大丈夫です。irbをもうひとつ立ち上げて、クライアント側から(new_with_uriを使って)サーバーのフロントオブジェクトを取得するスクリプトを見ます。このエントリではこの2つのirbをそのまま使い続け、どんどん追記していきます。どちらのことか判断しやすいように、各々[サーバー側]と[クライアント側]という風に明記することにします。
[クライアント側]
irb(main):001:0> require 'drb/drb'
=> true
irb(main):002:0> there = DRbObject.new_with_uri('druby://サーバーのホスト名:9898')
=> #<DRb::DRbObject:0x29a5f68 @ref=nil, @uri="druby://サーバーのホスト名:9898">
この場合、@refはnilです。本にも
@refがnilの場合は特別にURIに関連づけられたフロントオブジェクトを参照します。
4.1.2 dRubyでは?
と記載がありますね。ちなみに前にも見たかもしれないですが、DRbObjectのnew_with_uri(uri)は、DRbObject.new(nil, uri)と同じです。
# Create a new DRbObject from a URI alone.
def self.new_with_uri(uri)
self.new(nil, uri)
end
では、クライアントのirbから操作を続けてみます。
[クライアント側]
irb(main):003:0> s = there[1]
=> #<DRb::DRbObject:0x29a59a0 @ref=21680810, @uri="druby://サーバーのホスト名:9898">
これは、先ほどサーバーで作成したDRbObjectです。@refは21680810ですね。サーバー側のオブジェクトIDが設定されています。参照オブジェクトに対する操作をクライアント側で引き続き行ってみます。
[クライアント側]
irb(main):004:0> s.upcase!
これをしてから、サーバー側のirbを操作して中身を確認してみましょう。
[サーバー側]
irb(main):009:0> str
=> "FOO"
irb(main):010:0> ref.to_s
=> "FOO"
irb(main):011:0> h[1].to_s
=> "FOO"
upcase!は、文字をすべて大文字に書き換えるメソッドです。はい、ちゃんとサーバー側のオブジェクトの実体(IDが21680810のオブジェクト)に変更が加えられていました。めでたし。
ところで、ここでちょっと疑問です。どうやらオブジェクトのIDを知っていることで、そのIDに紐付いたオブジェクトの実体を触れるようですが、いったいどのような仕組みになっているのでしょうか。仕組みを知るためにDRbServerのソースを見てみます。
# Convert a dRuby reference to the local object it refers to.
def to_obj(ref)
return front if ref.nil?
return front[ref.to_s] if DRbURIOption === ref
@idconv.to_obj(ref)
end
ここで、今回関係ある部分は「@idconv.to_obj(ref)」です。@idconvは、デフォルトではDRbIdConvのインスタンスが設定されているようなので、このクラスを見ることにします。
class DRbIdConv
# Convert an object reference id to an object.
#
# This implementation looks up the reference id in the local object
# space and returns the object it refers to.
def to_obj(ref)
ObjectSpace._id2ref(ref)
end
・・・
ここではObjectSpaceというものを使って変換しているようです。Rubyのリファレンスを見てみます。
ObjectSpace
全てのオブジェクトを操作するためのモジュール。モジュール関数:
ObjectSpace._id2ref(id)
オブジェクト ID(Object#__id__)からオブジェクトを得ます。対応するオブジェクトが存在しなければ例外 RangeError が発生します。Rubyリファレンスマニュアル - http://www.ruby-lang.org/ja/man/index.cgi?cmd=view;name=ObjectSpace
ObjectSpaceモジュールとは、どうやらRubyスクリプト内で実体化されたすべてのインスタンスの集合に対して色々できるやつのようです。なるほど、こういうやつが居るんですね。わかったので先へ進みます。
では、次はクライアントのirbから参照を渡します。ちゃんとstart_serviceしておきます。引数を指定しない場合、空いている適当なポート番号が割り当てられます。ここでは30273が割り当てられました。
[クライアント側]
irb(main):005:0> DRb.start_service
=> #<DRb::DRbServer:0x296448c ・・・・
irb(main):006:0> str = 'bar'
=> "bar"
irb(main):007:0> there[2] = DRbObject.new(str)
=> #<DRb::DRbObject:0x2937ce8 @ref=21696060, @uri="druby://クライアントのホスト名:30273">
そしてサーバー側で内容を書き換えます。
[サーバー側]
irb(main):012:0> h[2]
=> #<DRb::DRbObject:0x297898c @ref=21696060, @uri="druby://クライアントのホスト名:30273">
irb(main):013:0> h[2].upcase!
=> "BAR"
クライアント側の実体が書き換えられていることを確認します。
[クライアント側]
irb(main):008:0> str
=> "BAR"
はい、ちゃんと書き換えられていますね。最後に、ここでクライアントのirbを終わらせてから、クライアントが実体を持っている参照オブジェクトに対してサーバーから操作してみましょう。エラーになるはずです。
[サーバー側]
irb(main):014:0> h[2].to_s
DRb::DRbConnError: druby://クライアントのホスト名:30273 - #<Errno::EBADF: Bad file descriptor - connect(2)>
DRbObjectについての理解がだいぶ深まったような気がします。なお、このエントリでは、本の中で紹介しているものとほぼ一緒の内容を試していますが、細かな手順が結構違っています。なのでこのエントリを読まれた方も是非一度、本の「4.1.2 dRubyでは?」に載っている手順に沿っておさらいしてみて下さい。次回はその続き、「4.2 自動的な参照渡し」に入るつもりです。