require 'socket' module DRuby # A DRuby session sends messages back and forth between server and client # The message format is as follows: # +-----+-----+-----+-----+-----+--- + ---+-----+ # | msg. size | msg. type | marshalled msg/args | # +-----+-----+-----+-----+-----+--- + ---+-----+ class Session REQUEST = 0x1001 REQUEST_BLOCK = 0x1002 RETVAL = 0x2001 EXCEPTION = 0x2002 YIELD = 0x2003 def initialize(io) @io = io end def send_message(type, obj) data = Marshal.dump(obj) header = [data.length, type] @io.write(header.pack("vv")) @io.write(data) end def get_message() header = @io.read(4) size, type = header.unpack("vv") data = @io.read(size) obj = Marshal.load(data) return [ type, obj ] end def finished() return @io.eof end end # The DRuby server class. Like its drb equivalent, this class spawns off # a new thread which processes requests, allowing the server to do other # things while it is doing processing for a distributed object. This # means, though, that all objects used with DRuby must be thread-safe. class Server attr_reader :host, :port, :obj, :thread def initialize(host, port, obj) @host = host @port = port @obj = obj @thread = Thread.new do server = TCPServer.new(@host, @port) while(socket = server.accept) socket.setsockopt(Socket::SOL_TCP, Socket::TCP_NODELAY, 1) Thread.new(socket) do |socket| begin session_loop(Session.new(socket)) rescue Exception # Let the user know we got an exception and exit # the loop puts $! puts $!.backtrace end end end end end # Main server loop. Wait for a REQUEST message, process it, and send # back a YIELD, EXCEPTION, or RETVAL message. def session_loop(session) while not session.finished() type, message = session.get_message() begin case type when Session::REQUEST retval = @obj.__send__(*message) when Session::REQUEST_BLOCK retval = @obj.__send__(*message) do |*i| session.send_message(Session::YIELD, i) end else # This should not happen raise ArgumentError end session.send_message(Session::RETVAL, retval) rescue Exception session.send_message(Session::EXCEPTION, $!) end end end end # The DRuby client class. A DRuby server must be started on the given # host and port before instantiating a DRuby client. class Client attr_reader :host, :port def initialize(host, port) @host = host @port = port @server = TCPSocket.open(@host, @port) @server.setsockopt(Socket::SOL_TCP, Socket::TCP_NODELAY, 1) @session = Session.new(@server) end # Client request handler. The idea here is to send out a REQUEST # message, and get back a YIELD, EXCEPTION, or RETVAL message. def method_missing(method, *args) message = [ method, *args ] @session.send_message( (block_given?) ? Session::REQUEST_BLOCK : Session::REQUEST, message) loop do type, message = @session.get_message() case type when Session::RETVAL ; return message when Session::YIELD ; yield message when Session::EXCEPTION ; raise message else ; raise RuntimeError end end end end end