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