1 require 'socket'
  2 
  3 module DRuby
  4 
  5     # A DRuby session sends messages back and forth between server and client
  6     # The message format is as follows:
  7     # +-----+-----+-----+-----+-----+---  +  ---+-----+
  8     # | msg. size | msg. type | marshalled msg/args   |
  9     # +-----+-----+-----+-----+-----+---  +  ---+-----+
 10     class Session
 11         REQUEST         = 0x1001
 12         REQUEST_BLOCK   = 0x1002
 13         RETVAL          = 0x2001
 14         EXCEPTION       = 0x2002
 15         YIELD           = 0x2003
 16 
 17         def initialize(io)
 18             @io = io
 19         end
 20 
 21         def send_message(type, obj)
 22             data = Marshal.dump(obj)
 23             header = [data.length, type]
 24             @io.write(header.pack("vv"))
 25             @io.write(data)
 26         end
 27 
 28         def get_message()
 29             header = @io.read(4)
 30             size, type = header.unpack("vv")
 31             data = @io.read(size)
 32             obj = Marshal.load(data)
 33             return [ type, obj ]
 34         end
 35 
 36         def finished()
 37             return @io.eof
 38         end
 39     end
 40 
 41     # The DRuby server class.  Like its drb equivalent, this class spawns off
 42     # a new thread which processes requests, allowing the server to do other
 43     # things while it is doing processing for a distributed object.  This
 44     # means, though, that all objects used with DRuby must be thread-safe.
 45     class Server
 46         attr_reader :host, :port, :obj, :thread
 47 
 48         def initialize(host, port, obj)
 49             @host = host
 50             @port = port
 51             @obj = obj
 52             @thread = Thread.new do
 53                 server = TCPServer.new(@host, @port)
 54                 while(socket = server.accept)
 55                     socket.setsockopt(Socket::SOL_TCP, Socket::TCP_NODELAY, 1)
 56                     Thread.new(socket) do |socket|
 57                         begin
 58                             session_loop(Session.new(socket))
 59                         rescue Exception
 60                             # Let the user know we got an exception and exit
 61                             # the loop
 62                             puts $!
 63                             puts $!.backtrace
 64                         end
 65                     end
 66                 end
 67             end
 68         end
 69 
 70         # Main server loop.  Wait for a REQUEST message, process it, and send
 71         # back a YIELD, EXCEPTION, or RETVAL message.
 72         def session_loop(session)
 73             while not session.finished()
 74                 type, message = session.get_message()
 75                 begin
 76                     case type
 77                         when Session::REQUEST
 78                             retval = @obj.__send__(*message)
 79                         when Session::REQUEST_BLOCK
 80                             retval = @obj.__send__(*message) do |*i|
 81                                 session.send_message(Session::YIELD, i)
 82                             end
 83                         else
 84                             # This should not happen
 85                             raise ArgumentError
 86                     end
 87                     session.send_message(Session::RETVAL, retval)
 88                 rescue Exception
 89                     session.send_message(Session::EXCEPTION, $!)
 90                 end
 91             end
 92         end
 93     end
 94 
 95     # The DRuby client class.  A DRuby server must be started on the given
 96     # host and port before instantiating a DRuby client.
 97     class Client
 98         attr_reader :host, :port
 99         
100         def initialize(host, port)
101             @host = host
102             @port = port
103             @server = TCPSocket.open(@host, @port)
104             @server.setsockopt(Socket::SOL_TCP, Socket::TCP_NODELAY, 1)
105             @session = Session.new(@server)
106         end
107 
108         # Client request handler.  The idea here is to send out a REQUEST
109         # message, and get back a YIELD, EXCEPTION, or RETVAL message.
110         def method_missing(method, *args)
111             message = [ method, *args ]
112             @session.send_message(
113                 (block_given?) ? Session::REQUEST_BLOCK : Session::REQUEST,
114                 message)
115             loop do
116                 type, message = @session.get_message()
117                 case type
118                     when Session::RETVAL     ; return message
119                     when Session::YIELD      ; yield message
120                     when Session::EXCEPTION  ; raise message
121                     else                     ; raise RuntimeError
122                 end
123             end
124         end
125     end
126 
127 end