rinda2 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 13d1278aefb6ed5124732435a552d304d7fb4138
4
- data.tar.gz: 0b8a86151cb6881f59eaf7d328f686ed8f401c0b
3
+ metadata.gz: f23287f91efcdf6b8dbdd11e8b0913c6739cf133
4
+ data.tar.gz: d556c379195055cec95457e55477bb11750c229a
5
5
  SHA512:
6
- metadata.gz: 125cd4ac651251f21a637431194837d15e617a87c599b1024ab9eb76336fb211e78dad59646307e06a6516f837238aacb6cdb8306c61cc3907834b3671a448c1
7
- data.tar.gz: 78ac6e59f23304a346a99f3eeffb868e64959248ce2cc3b1705ed6ad68399a85d3bc066582037ff71425b3657bcbfadea7fa1ddd93e1f4a3d999290399551de7
6
+ metadata.gz: 41702362cfae06f7822c6849861a5c21d6652888f99cfc3b8b34651664439be6be8c2a702eb9ba6b82ee87bf52bfea26df4c4638eb0f9fd67638ee794e20426c
7
+ data.tar.gz: 64afa0d0fc633c205596b21573ee68cb222f1d345d63c2d1a3db7f94184c0f6e6974ad5febb85fb25d5a2d74d8a1ddd93b61174863fcf108fd5c346089ac5325
@@ -1,7 +1,7 @@
1
- require "rinda2/version"
2
- require "rinda2/ring"
3
- require "rinda2/rinda"
4
- require "rinda2/tuplespace"
1
+ require_relative "rinda2/version"
2
+ require_relative "rinda2/ring"
3
+ require_relative "rinda2/rinda"
4
+ require_relative "rinda2/tuplespace"
5
5
 
6
6
  module Rinda2
7
7
  # Your code goes here...
@@ -3,10 +3,13 @@
3
3
  # Note: Rinda::Ring API is unstable.
4
4
  #
5
5
  require 'drb/drb'
6
- require 'rinda/rinda'
7
6
  require 'thread'
8
7
  require 'ipaddr'
9
8
 
9
+ require_relative 'rinda'
10
+ require_relative 'ring/finger'
11
+ require_relative 'ring/server'
12
+
10
13
  module Rinda
11
14
 
12
15
  ##
@@ -14,448 +17,6 @@ module Rinda
14
17
 
15
18
  Ring_PORT = 7647
16
19
 
17
- ##
18
- # A RingServer allows a Rinda::TupleSpace to be located via UDP broadcasts.
19
- # Default service location uses the following steps:
20
- #
21
- # 1. A RingServer begins listening on the network broadcast UDP address.
22
- # 2. A RingFinger sends a UDP packet containing the DRb URI where it will
23
- # listen for a reply.
24
- # 3. The RingServer receives the UDP packet and connects back to the
25
- # provided DRb URI with the DRb service.
26
- #
27
- # A RingServer requires a TupleSpace:
28
- #
29
- # ts = Rinda::TupleSpace.new
30
- # rs = Rinda::RingServer.new
31
- #
32
- # RingServer can also listen on multicast addresses for announcements. This
33
- # allows multiple RingServers to run on the same host. To use network
34
- # broadcast and multicast:
35
- #
36
- # ts = Rinda::TupleSpace.new
37
- # rs = Rinda::RingServer.new ts, %w[Socket::INADDR_ANY, 239.0.0.1 ff02::1]
38
-
39
- class RingServer
40
-
41
- include DRbUndumped
42
-
43
- ##
44
- # Special renewer for the RingServer to allow shutdown
45
-
46
- class Renewer # :nodoc:
47
- include DRbUndumped
48
-
49
- ##
50
- # Set to false to shutdown future requests using this Renewer
51
-
52
- attr_writer :renew
53
-
54
- def initialize # :nodoc:
55
- @renew = true
56
- end
57
-
58
- def renew # :nodoc:
59
- @renew ? 1 : true
60
- end
61
- end
62
-
63
- ##
64
- # Advertises +ts+ on the given +addresses+ at +port+.
65
- #
66
- # If +addresses+ is omitted only the UDP broadcast address is used.
67
- #
68
- # +addresses+ can contain multiple addresses. If a multicast address is
69
- # given in +addresses+ then the RingServer will listen for multicast
70
- # queries.
71
- #
72
- # If you use IPv4 multicast you may need to set an address of the inbound
73
- # interface which joins a multicast group.
74
- #
75
- # ts = Rinda::TupleSpace.new
76
- # rs = Rinda::RingServer.new(ts, [['239.0.0.1', '9.5.1.1']])
77
- #
78
- # You can set addresses as an Array Object. The first element of the
79
- # Array is a multicast address and the second is an inbound interface
80
- # address. If the second is omitted then '0.0.0.0' is used.
81
- #
82
- # If you use IPv6 multicast you may need to set both the local interface
83
- # address and the inbound interface index:
84
- #
85
- # rs = Rinda::RingServer.new(ts, [['ff02::1', '::1', 1]])
86
- #
87
- # The first element is a multicast address and the second is an inbound
88
- # interface address. The third is an inbound interface index.
89
- #
90
- # At this time there is no easy way to get an interface index by name.
91
- #
92
- # If the second is omitted then '::1' is used.
93
- # If the third is omitted then 0 (default interface) is used.
94
-
95
- def initialize(ts, addresses=[Socket::INADDR_ANY], port=Ring_PORT)
96
- @port = port
97
-
98
- if Integer === addresses then
99
- addresses, @port = [Socket::INADDR_ANY], addresses
100
- end
101
-
102
- @renewer = Renewer.new
103
-
104
- @ts = ts
105
- @sockets = []
106
- addresses.each do |address|
107
- if Array === address
108
- make_socket(*address)
109
- else
110
- make_socket(address)
111
- end
112
- end
113
-
114
- @w_services = write_services
115
- @r_service = reply_service
116
- end
117
-
118
- ##
119
- # Creates a socket at +address+
120
- #
121
- # If +address+ is multicast address then +interface_address+ and
122
- # +multicast_interface+ can be set as optional.
123
- #
124
- # A created socket is bound to +interface_address+. If you use IPv4
125
- # multicast then the interface of +interface_address+ is used as the
126
- # inbound interface. If +interface_address+ is omitted or nil then
127
- # '0.0.0.0' or '::1' is used.
128
- #
129
- # If you use IPv6 multicast then +multicast_interface+ is used as the
130
- # inbound interface. +multicast_interface+ is a network interface index.
131
- # If +multicast_interface+ is omitted then 0 (default interface) is used.
132
-
133
- def make_socket(address, interface_address=nil, multicast_interface=0)
134
- addrinfo = Addrinfo.udp(address, @port)
135
-
136
- socket = Socket.new(addrinfo.pfamily, addrinfo.socktype,
137
- addrinfo.protocol)
138
- @sockets << socket
139
-
140
- if addrinfo.ipv4_multicast? or addrinfo.ipv6_multicast? then
141
- if Socket.const_defined?(:SO_REUSEPORT) then
142
- socket.setsockopt(:SOCKET, :SO_REUSEPORT, true)
143
- else
144
- socket.setsockopt(:SOCKET, :SO_REUSEADDR, true)
145
- end
146
-
147
- if addrinfo.ipv4_multicast? then
148
- interface_address = '0.0.0.0' if interface_address.nil?
149
- socket.bind(Addrinfo.udp('0.0.0.0', @port))
150
-
151
- mreq = IPAddr.new(addrinfo.ip_address).hton +
152
- IPAddr.new(interface_address).hton
153
-
154
- socket.setsockopt(:IPPROTO_IP, :IP_ADD_MEMBERSHIP, mreq)
155
- else
156
- interface_address = '::1' if interface_address.nil?
157
- socket.bind(Addrinfo.udp(interface_address, @port))
158
-
159
- mreq = IPAddr.new(addrinfo.ip_address).hton +
160
- [multicast_interface].pack('I')
161
-
162
- socket.setsockopt(:IPPROTO_IPV6, :IPV6_JOIN_GROUP, mreq)
163
- end
164
- else
165
- socket.bind(addrinfo)
166
- end
167
-
168
- socket
169
- end
170
-
171
- ##
172
- # Creates threads that pick up UDP packets and passes them to do_write for
173
- # decoding.
174
-
175
- def write_services
176
- @sockets.map do |s|
177
- Thread.new(s) do |socket|
178
- loop do
179
- msg = socket.recv(1024)
180
- do_write(msg)
181
- end
182
- end
183
- end
184
- end
185
-
186
- ##
187
- # Extracts the response URI from +msg+ and adds it to TupleSpace where it
188
- # will be picked up by +reply_service+ for notification.
189
-
190
- def do_write(msg)
191
- Thread.new do
192
- begin
193
- tuple, sec = Marshal.load(msg)
194
- @ts.write(tuple, sec)
195
- rescue
196
- end
197
- end
198
- end
199
-
200
- ##
201
- # Creates a thread that notifies waiting clients from the TupleSpace.
202
-
203
- def reply_service
204
- Thread.new do
205
- loop do
206
- do_reply
207
- end
208
- end
209
- end
210
-
211
- ##
212
- # Pulls lookup tuples out of the TupleSpace and sends their DRb object the
213
- # address of the local TupleSpace.
214
-
215
- def do_reply
216
- tuple = @ts.take([:lookup_ring, nil], @renewer)
217
- Thread.new { tuple[1].call(@ts) rescue nil}
218
- rescue
219
- end
220
-
221
- ##
222
- # Shuts down the RingServer
223
-
224
- def shutdown
225
- @renewer.renew = false
226
-
227
- @w_services.each do |thread|
228
- thread.kill
229
- thread.join
230
- end
231
-
232
- @sockets.each do |socket|
233
- socket.close
234
- end
235
-
236
- @r_service.kill
237
- @r_service.join
238
- end
239
-
240
- end
241
-
242
- ##
243
- # RingFinger is used by RingServer clients to discover the RingServer's
244
- # TupleSpace. Typically, all a client needs to do is call
245
- # RingFinger.primary to retrieve the remote TupleSpace, which it can then
246
- # begin using.
247
- #
248
- # To find the first available remote TupleSpace:
249
- #
250
- # Rinda::RingFinger.primary
251
- #
252
- # To create a RingFinger that broadcasts to a custom list:
253
- #
254
- # rf = Rinda::RingFinger.new ['localhost', '192.0.2.1']
255
- # rf.primary
256
- #
257
- # Rinda::RingFinger also understands multicast addresses and sets them up
258
- # properly. This allows you to run multiple RingServers on the same host:
259
- #
260
- # rf = Rinda::RingFinger.new ['239.0.0.1']
261
- # rf.primary
262
- #
263
- # You can set the hop count (or TTL) for multicast searches using
264
- # #multicast_hops.
265
- #
266
- # If you use IPv6 multicast you may need to set both an address and the
267
- # outbound interface index:
268
- #
269
- # rf = Rinda::RingFinger.new ['ff02::1']
270
- # rf.multicast_interface = 1
271
- # rf.primary
272
- #
273
- # At this time there is no easy way to get an interface index by name.
274
-
275
- class RingFinger
276
-
277
- @@broadcast_list = ['<broadcast>', 'localhost']
278
-
279
- @@finger = nil
280
-
281
- ##
282
- # Creates a singleton RingFinger and looks for a RingServer. Returns the
283
- # created RingFinger.
284
-
285
- def self.finger
286
- unless @@finger
287
- @@finger = self.new
288
- @@finger.lookup_ring_any
289
- end
290
- @@finger
291
- end
292
-
293
- ##
294
- # Returns the first advertised TupleSpace.
295
-
296
- def self.primary
297
- finger.primary
298
- end
299
-
300
- ##
301
- # Contains all discovered TupleSpaces except for the primary.
302
-
303
- def self.to_a
304
- finger.to_a
305
- end
306
-
307
- ##
308
- # The list of addresses where RingFinger will send query packets.
309
-
310
- attr_accessor :broadcast_list
311
-
312
- ##
313
- # Maximum number of hops for sent multicast packets (if using a multicast
314
- # address in the broadcast list). The default is 1 (same as UDP
315
- # broadcast).
316
-
317
- attr_accessor :multicast_hops
318
-
319
- ##
320
- # The interface index to send IPv6 multicast packets from.
321
-
322
- attr_accessor :multicast_interface
323
-
324
- ##
325
- # The port that RingFinger will send query packets to.
326
-
327
- attr_accessor :port
328
-
329
- ##
330
- # Contain the first advertised TupleSpace after lookup_ring_any is called.
331
-
332
- attr_accessor :primary
333
-
334
- ##
335
- # Creates a new RingFinger that will look for RingServers at +port+ on
336
- # the addresses in +broadcast_list+.
337
- #
338
- # If +broadcast_list+ contains a multicast address then multicast queries
339
- # will be made using the given multicast_hops and multicast_interface.
340
-
341
- def initialize(broadcast_list=@@broadcast_list, port=Ring_PORT)
342
- @broadcast_list = broadcast_list || ['localhost']
343
- @port = port
344
- @primary = nil
345
- @rings = []
346
-
347
- @multicast_hops = 1
348
- @multicast_interface = 0
349
- end
350
-
351
- ##
352
- # Contains all discovered TupleSpaces except for the primary.
353
-
354
- def to_a
355
- @rings
356
- end
357
-
358
- ##
359
- # Iterates over all discovered TupleSpaces starting with the primary.
360
-
361
- def each
362
- lookup_ring_any unless @primary
363
- return unless @primary
364
- yield(@primary)
365
- @rings.each { |x| yield(x) }
366
- end
367
-
368
- ##
369
- # Looks up RingServers waiting +timeout+ seconds. RingServers will be
370
- # given +block+ as a callback, which will be called with the remote
371
- # TupleSpace.
372
-
373
- def lookup_ring(timeout=5, &block)
374
- return lookup_ring_any(timeout) unless block_given?
375
-
376
- msg = Marshal.dump([[:lookup_ring, DRbObject.new(block)], timeout])
377
- @broadcast_list.each do |it|
378
- send_message(it, msg)
379
- end
380
- sleep(timeout)
381
- end
382
-
383
- ##
384
- # Returns the first found remote TupleSpace. Any further recovered
385
- # TupleSpaces can be found by calling +to_a+.
386
-
387
- def lookup_ring_any(timeout=5)
388
- queue = Queue.new
389
-
390
- Thread.new do
391
- self.lookup_ring(timeout) do |ts|
392
- queue.push(ts)
393
- end
394
- queue.push(nil)
395
- end
396
-
397
- @primary = queue.pop
398
- raise('RingNotFound') if @primary.nil?
399
-
400
- Thread.new do
401
- while it = queue.pop
402
- @rings.push(it)
403
- end
404
- end
405
-
406
- @primary
407
- end
408
-
409
- ##
410
- # Creates a socket for +address+ with the appropriate multicast options
411
- # for multicast addresses.
412
-
413
- def make_socket(address, interface_address = nil) # :nodoc:
414
- addrinfo = Addrinfo.udp(address, @port)
415
-
416
- soc = Socket.new(addrinfo.pfamily, addrinfo.socktype, addrinfo.protocol)
417
- begin
418
- if addrinfo.ipv4_multicast? then
419
- interface_address ||= '0.0.0.0'
420
- soc.setsockopt(Socket::Option.ipv4_multicast_loop(1))
421
- soc.setsockopt(Socket::Option.ipv4_multicast_ttl(@multicast_hops))
422
- ifreq = IPAddr.new(interface_address).hton
423
- soc.setsockopt(:IPPROTO_IP, :IP_MULTICAST_IF, ifreq)
424
- elsif addrinfo.ipv6_multicast? then
425
- soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_LOOP, true)
426
- soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_HOPS,
427
- [@multicast_hops].pack('I'))
428
- soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_IF,
429
- [@multicast_interface].pack('I'))
430
- else
431
- soc.setsockopt(:SOL_SOCKET, :SO_BROADCAST, true)
432
- end
433
-
434
- soc.connect(addrinfo)
435
- rescue Exception
436
- soc.close
437
- raise
438
- end
439
-
440
- soc
441
- end
442
-
443
- def send_message(address, message) # :nodoc:
444
- soc = if Array === address
445
- make_socket(*address)
446
- else
447
- make_socket(address)
448
- end
449
-
450
- soc.send(message, 0)
451
- rescue
452
- nil
453
- ensure
454
- soc.close if soc
455
- end
456
-
457
- end
458
-
459
20
  ##
460
21
  # RingProvider uses a RingServer advertised TupleSpace as a name service.
461
22
  # TupleSpace clients can register themselves with the remote TupleSpace and
@@ -0,0 +1,222 @@
1
+ module Rinda
2
+
3
+ ##
4
+ # RingFinger is used by RingServer clients to discover the RingServer's
5
+ # TupleSpace. Typically, all a client needs to do is call
6
+ # RingFinger.primary to retrieve the remote TupleSpace, which it can then
7
+ # begin using.
8
+ #
9
+ # To find the first available remote TupleSpace:
10
+ #
11
+ # Rinda::RingFinger.primary
12
+ #
13
+ # To create a RingFinger that broadcasts to a custom list:
14
+ #
15
+ # rf = Rinda::RingFinger.new ['localhost', '192.0.2.1']
16
+ # rf.primary
17
+ #
18
+ # Rinda::RingFinger also understands multicast addresses and sets them up
19
+ # properly. This allows you to run multiple RingServers on the same host:
20
+ #
21
+ # rf = Rinda::RingFinger.new ['239.0.0.1']
22
+ # rf.primary
23
+ #
24
+ # You can set the hop count (or TTL) for multicast searches using
25
+ # #multicast_hops.
26
+ #
27
+ # If you use IPv6 multicast you may need to set both an address and the
28
+ # outbound interface index:
29
+ #
30
+ # rf = Rinda::RingFinger.new ['ff02::1']
31
+ # rf.multicast_interface = 1
32
+ # rf.primary
33
+ #
34
+ # At this time there is no easy way to get an interface index by name.
35
+
36
+ class RingFinger
37
+
38
+ @@broadcast_list = ['<broadcast>', 'localhost']
39
+
40
+ @@finger = nil
41
+
42
+ ##
43
+ # Creates a singleton RingFinger and looks for a RingServer. Returns the
44
+ # created RingFinger.
45
+
46
+ def self.finger
47
+ unless @@finger
48
+ @@finger = self.new
49
+ @@finger.lookup_ring_any
50
+ end
51
+ @@finger
52
+ end
53
+
54
+ ##
55
+ # Returns the first advertised TupleSpace.
56
+
57
+ def self.primary
58
+ finger.primary
59
+ end
60
+
61
+ ##
62
+ # Contains all discovered TupleSpaces except for the primary.
63
+
64
+ def self.to_a
65
+ finger.to_a
66
+ end
67
+
68
+ ##
69
+ # The list of addresses where RingFinger will send query packets.
70
+
71
+ attr_accessor :broadcast_list
72
+
73
+ ##
74
+ # Maximum number of hops for sent multicast packets (if using a multicast
75
+ # address in the broadcast list). The default is 1 (same as UDP
76
+ # broadcast).
77
+
78
+ attr_accessor :multicast_hops
79
+
80
+ ##
81
+ # The interface index to send IPv6 multicast packets from.
82
+
83
+ attr_accessor :multicast_interface
84
+
85
+ ##
86
+ # The port that RingFinger will send query packets to.
87
+
88
+ attr_accessor :port
89
+
90
+ ##
91
+ # Contain the first advertised TupleSpace after lookup_ring_any is called.
92
+
93
+ attr_accessor :primary
94
+
95
+ ##
96
+ # Creates a new RingFinger that will look for RingServers at +port+ on
97
+ # the addresses in +broadcast_list+.
98
+ #
99
+ # If +broadcast_list+ contains a multicast address then multicast queries
100
+ # will be made using the given multicast_hops and multicast_interface.
101
+
102
+ def initialize(broadcast_list=@@broadcast_list, port=Ring_PORT)
103
+ @broadcast_list = broadcast_list || ['localhost']
104
+ @port = port
105
+ @primary = nil
106
+ @rings = []
107
+
108
+ @multicast_hops = 1
109
+ @multicast_interface = 0
110
+ end
111
+
112
+ ##
113
+ # Contains all discovered TupleSpaces except for the primary.
114
+
115
+ def to_a
116
+ @rings
117
+ end
118
+
119
+ ##
120
+ # Iterates over all discovered TupleSpaces starting with the primary.
121
+
122
+ def each
123
+ Enumerator.new do |y|
124
+ lookup_ring_any unless @primary
125
+ return unless @primary
126
+ y << @primary
127
+ @rings.each { |x| y << x }
128
+ end
129
+ end
130
+
131
+ ##
132
+ # Looks up RingServers waiting +timeout+ seconds. RingServers will be
133
+ # given +block+ as a callback, which will be called with the remote
134
+ # TupleSpace.
135
+
136
+ def lookup_ring(timeout=5, &block)
137
+ return lookup_ring_any(timeout) unless block_given?
138
+
139
+ msg = Marshal.dump([[:lookup_ring, DRbObject.new(block)], timeout])
140
+ @broadcast_list.each do |it|
141
+ send_message(it, msg)
142
+ end
143
+ sleep(timeout)
144
+ end
145
+
146
+ ##
147
+ # Returns the first found remote TupleSpace. Any further recovered
148
+ # TupleSpaces can be found by calling +to_a+.
149
+
150
+ def lookup_ring_any(timeout=5)
151
+ queue = Queue.new
152
+
153
+ Thread.new do
154
+ self.lookup_ring(timeout) do |ts|
155
+ queue.push(ts)
156
+ end
157
+ queue.push(nil)
158
+ end
159
+
160
+ @primary = queue.pop
161
+ raise('RingNotFound') if @primary.nil?
162
+
163
+ Thread.new do
164
+ while it = queue.pop
165
+ @rings.push(it)
166
+ end
167
+ end
168
+
169
+ @primary
170
+ end
171
+
172
+ ##
173
+ # Creates a socket for +address+ with the appropriate multicast options
174
+ # for multicast addresses.
175
+
176
+ def make_socket(address, interface_address = nil) # :nodoc:
177
+ addrinfo = Addrinfo.udp(address, @port)
178
+
179
+ soc = Socket.new(addrinfo.pfamily, addrinfo.socktype, addrinfo.protocol)
180
+ begin
181
+ if addrinfo.ipv4_multicast? then
182
+ interface_address ||= '0.0.0.0'
183
+ soc.setsockopt(Socket::Option.ipv4_multicast_loop(1))
184
+ soc.setsockopt(Socket::Option.ipv4_multicast_ttl(@multicast_hops))
185
+ ifreq = IPAddr.new(interface_address).hton
186
+ soc.setsockopt(:IPPROTO_IP, :IP_MULTICAST_IF, ifreq)
187
+ elsif addrinfo.ipv6_multicast? then
188
+ soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_LOOP, true)
189
+ soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_HOPS,
190
+ [@multicast_hops].pack('I'))
191
+ soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_IF,
192
+ [@multicast_interface].pack('I'))
193
+ else
194
+ soc.setsockopt(:SOL_SOCKET, :SO_BROADCAST, true)
195
+ end
196
+
197
+ soc.connect(addrinfo)
198
+ rescue Exception
199
+ soc.close
200
+ raise
201
+ end
202
+
203
+ soc
204
+ end
205
+
206
+ def send_message(address, message) # :nodoc:
207
+ soc = if Array === address
208
+ make_socket(*address)
209
+ else
210
+ make_socket(address)
211
+ end
212
+
213
+ soc.send(message, 0)
214
+ rescue
215
+ nil
216
+ ensure
217
+ soc.close if soc
218
+ end
219
+
220
+ end
221
+
222
+ end
@@ -0,0 +1,228 @@
1
+ module Rinda
2
+
3
+ ##
4
+ # A RingServer allows a Rinda::TupleSpace to be located via UDP broadcasts.
5
+ # Default service location uses the following steps:
6
+ #
7
+ # 1. A RingServer begins listening on the network broadcast UDP address.
8
+ # 2. A RingFinger sends a UDP packet containing the DRb URI where it will
9
+ # listen for a reply.
10
+ # 3. The RingServer receives the UDP packet and connects back to the
11
+ # provided DRb URI with the DRb service.
12
+ #
13
+ # A RingServer requires a TupleSpace:
14
+ #
15
+ # ts = Rinda::TupleSpace.new
16
+ # rs = Rinda::RingServer.new
17
+ #
18
+ # RingServer can also listen on multicast addresses for announcements. This
19
+ # allows multiple RingServers to run on the same host. To use network
20
+ # broadcast and multicast:
21
+ #
22
+ # ts = Rinda::TupleSpace.new
23
+ # rs = Rinda::RingServer.new ts, %w[Socket::INADDR_ANY, 239.0.0.1 ff02::1]
24
+
25
+ class RingServer
26
+
27
+ include DRbUndumped
28
+
29
+ ##
30
+ # Special renewer for the RingServer to allow shutdown
31
+
32
+ class Renewer # :nodoc:
33
+ include DRbUndumped
34
+
35
+ ##
36
+ # Set to false to shutdown future requests using this Renewer
37
+
38
+ attr_writer :renew
39
+
40
+ def initialize # :nodoc:
41
+ @renew = true
42
+ end
43
+
44
+ def renew # :nodoc:
45
+ @renew ? 1 : true
46
+ end
47
+ end
48
+
49
+ ##
50
+ # Advertises +ts+ on the given +addresses+ at +port+.
51
+ #
52
+ # If +addresses+ is omitted only the UDP broadcast address is used.
53
+ #
54
+ # +addresses+ can contain multiple addresses. If a multicast address is
55
+ # given in +addresses+ then the RingServer will listen for multicast
56
+ # queries.
57
+ #
58
+ # If you use IPv4 multicast you may need to set an address of the inbound
59
+ # interface which joins a multicast group.
60
+ #
61
+ # ts = Rinda::TupleSpace.new
62
+ # rs = Rinda::RingServer.new(ts, [['239.0.0.1', '9.5.1.1']])
63
+ #
64
+ # You can set addresses as an Array Object. The first element of the
65
+ # Array is a multicast address and the second is an inbound interface
66
+ # address. If the second is omitted then '0.0.0.0' is used.
67
+ #
68
+ # If you use IPv6 multicast you may need to set both the local interface
69
+ # address and the inbound interface index:
70
+ #
71
+ # rs = Rinda::RingServer.new(ts, [['ff02::1', '::1', 1]])
72
+ #
73
+ # The first element is a multicast address and the second is an inbound
74
+ # interface address. The third is an inbound interface index.
75
+ #
76
+ # At this time there is no easy way to get an interface index by name.
77
+ #
78
+ # If the second is omitted then '::1' is used.
79
+ # If the third is omitted then 0 (default interface) is used.
80
+
81
+ def initialize(ts, addresses=[Socket::INADDR_ANY], port=Ring_PORT)
82
+ @port = port
83
+
84
+ if Integer === addresses then
85
+ addresses, @port = [Socket::INADDR_ANY], addresses
86
+ end
87
+
88
+ @renewer = Renewer.new
89
+
90
+ @ts = ts
91
+ @sockets = []
92
+ addresses.each do |address|
93
+ if Array === address
94
+ make_socket(*address)
95
+ else
96
+ make_socket(address)
97
+ end
98
+ end
99
+
100
+ @w_services = write_services
101
+ @r_service = reply_service
102
+ end
103
+
104
+ ##
105
+ # Creates a socket at +address+
106
+ #
107
+ # If +address+ is multicast address then +interface_address+ and
108
+ # +multicast_interface+ can be set as optional.
109
+ #
110
+ # A created socket is bound to +interface_address+. If you use IPv4
111
+ # multicast then the interface of +interface_address+ is used as the
112
+ # inbound interface. If +interface_address+ is omitted or nil then
113
+ # '0.0.0.0' or '::1' is used.
114
+ #
115
+ # If you use IPv6 multicast then +multicast_interface+ is used as the
116
+ # inbound interface. +multicast_interface+ is a network interface index.
117
+ # If +multicast_interface+ is omitted then 0 (default interface) is used.
118
+
119
+ def make_socket(address, interface_address=nil, multicast_interface=0)
120
+ addrinfo = Addrinfo.udp(address, @port)
121
+
122
+ socket = Socket.new(addrinfo.pfamily, addrinfo.socktype,
123
+ addrinfo.protocol)
124
+ @sockets << socket
125
+
126
+ if addrinfo.ipv4_multicast? or addrinfo.ipv6_multicast? then
127
+ if Socket.const_defined?(:SO_REUSEPORT) then
128
+ socket.setsockopt(:SOCKET, :SO_REUSEPORT, true)
129
+ else
130
+ socket.setsockopt(:SOCKET, :SO_REUSEADDR, true)
131
+ end
132
+
133
+ if addrinfo.ipv4_multicast? then
134
+ interface_address = '0.0.0.0' if interface_address.nil?
135
+ socket.bind(Addrinfo.udp('0.0.0.0', @port))
136
+
137
+ mreq = IPAddr.new(addrinfo.ip_address).hton +
138
+ IPAddr.new(interface_address).hton
139
+
140
+ socket.setsockopt(:IPPROTO_IP, :IP_ADD_MEMBERSHIP, mreq)
141
+ else
142
+ interface_address = '::1' if interface_address.nil?
143
+ socket.bind(Addrinfo.udp(interface_address, @port))
144
+
145
+ mreq = IPAddr.new(addrinfo.ip_address).hton +
146
+ [multicast_interface].pack('I')
147
+
148
+ socket.setsockopt(:IPPROTO_IPV6, :IPV6_JOIN_GROUP, mreq)
149
+ end
150
+ else
151
+ socket.bind(addrinfo)
152
+ end
153
+
154
+ socket
155
+ end
156
+
157
+ ##
158
+ # Creates threads that pick up UDP packets and passes them to do_write for
159
+ # decoding.
160
+
161
+ def write_services
162
+ @sockets.map do |s|
163
+ Thread.new(s) do |socket|
164
+ loop do
165
+ msg = socket.recv(9000)
166
+ do_write(msg)
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ ##
173
+ # Extracts the response URI from +msg+ and adds it to TupleSpace where it
174
+ # will be picked up by +reply_service+ for notification.
175
+
176
+ def do_write(msg)
177
+ Thread.new do
178
+ begin
179
+ tuple, sec = Marshal.load(msg)
180
+ @ts.write(tuple, sec)
181
+ rescue
182
+ end
183
+ end
184
+ end
185
+
186
+ ##
187
+ # Creates a thread that notifies waiting clients from the TupleSpace.
188
+
189
+ def reply_service
190
+ Thread.new do
191
+ loop do
192
+ do_reply
193
+ end
194
+ end
195
+ end
196
+
197
+ ##
198
+ # Pulls lookup tuples out of the TupleSpace and sends their DRb object the
199
+ # address of the local TupleSpace.
200
+
201
+ def do_reply
202
+ tuple = @ts.take([:lookup_ring, nil], @renewer)
203
+ Thread.new { tuple[1].call(@ts) rescue nil}
204
+ rescue
205
+ end
206
+
207
+ ##
208
+ # Shuts down the RingServer
209
+
210
+ def shutdown
211
+ @renewer.renew = false
212
+
213
+ @w_services.each do |thread|
214
+ thread.kill
215
+ thread.join
216
+ end
217
+
218
+ @sockets.each do |socket|
219
+ socket.close
220
+ end
221
+
222
+ @r_service.kill
223
+ @r_service.join
224
+ end
225
+
226
+ end
227
+
228
+ end
@@ -2,8 +2,8 @@
2
2
  require 'monitor'
3
3
  require 'thread'
4
4
  require 'drb/drb'
5
- require 'rinda/rinda'
6
5
  require 'forwardable'
6
+ require_relative 'rinda'
7
7
 
8
8
  module Rinda
9
9
 
@@ -639,4 +639,3 @@ module Rinda
639
639
  end
640
640
 
641
641
  end
642
-
@@ -1,3 +1,3 @@
1
1
  module Rinda2
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rinda2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Baum
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-12-12 00:00:00.000000000 Z
11
+ date: 2016-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -55,6 +55,8 @@ files:
55
55
  - lib/rinda2.rb
56
56
  - lib/rinda2/rinda.rb
57
57
  - lib/rinda2/ring.rb
58
+ - lib/rinda2/ring/finger.rb
59
+ - lib/rinda2/ring/server.rb
58
60
  - lib/rinda2/tuplespace.rb
59
61
  - lib/rinda2/version.rb
60
62
  - rinda2.gemspec