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