zmachine 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/echo_client.rb +71 -0
- data/echo_server.rb +41 -0
- data/lib/zmachine/acceptor.rb +12 -0
- data/lib/zmachine/channel.rb +14 -0
- data/lib/zmachine/connection.rb +186 -0
- data/lib/zmachine/deferrable.rb +207 -0
- data/lib/zmachine/jeromq-0.3.0-SNAPSHOT.jar +0 -0
- data/lib/zmachine/reactor.rb +337 -0
- data/lib/zmachine/tcp_channel.rb +169 -0
- data/lib/zmachine/timers.rb +61 -0
- data/lib/zmachine/zmq_channel.rb +101 -0
- data/lib/zmachine.rb +171 -0
- data/zmachine.gemspec +20 -0
- metadata +102 -0
@@ -0,0 +1,337 @@
|
|
1
|
+
java_import java.io.FileDescriptor
|
2
|
+
java_import java.io.IOException
|
3
|
+
java_import java.net.InetSocketAddress
|
4
|
+
java_import java.nio.ByteBuffer
|
5
|
+
java_import java.nio.channels.ClosedChannelException
|
6
|
+
java_import java.nio.channels.SelectionKey
|
7
|
+
java_import java.nio.channels.Selector
|
8
|
+
java_import java.nio.channels.ServerSocketChannel
|
9
|
+
java_import java.nio.channels.SocketChannel
|
10
|
+
java_import java.util.TreeMap
|
11
|
+
java_import java.util.concurrent.atomic.AtomicBoolean
|
12
|
+
java_import java.util.concurrent.ConcurrentLinkedQueue
|
13
|
+
|
14
|
+
require 'zmachine/jeromq-0.3.0-SNAPSHOT.jar'
|
15
|
+
java_import org.zeromq.ZContext
|
16
|
+
|
17
|
+
require 'zmachine/acceptor'
|
18
|
+
require 'zmachine/tcp_channel'
|
19
|
+
require 'zmachine/zmq_channel'
|
20
|
+
|
21
|
+
module ZMachine
|
22
|
+
class Reactor
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@timers = TreeMap.new
|
26
|
+
@timer_callbacks = {}
|
27
|
+
@channels = []
|
28
|
+
@new_channels = []
|
29
|
+
@unbound_channels = []
|
30
|
+
@next_signature = 0
|
31
|
+
@shutdown_hooks = []
|
32
|
+
@next_tick_queue = ConcurrentLinkedQueue.new
|
33
|
+
@running = false
|
34
|
+
|
35
|
+
# don't use a direct buffer. Ruby doesn't seem to like them.
|
36
|
+
@read_buffer = ByteBuffer.allocate(32*1024)
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_shutdown_hook(&block)
|
40
|
+
@shutdown_hooks << block
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_timer(*args, &block)
|
44
|
+
interval = args.shift
|
45
|
+
callback = args.shift || block
|
46
|
+
return unless callback
|
47
|
+
|
48
|
+
signature = next_signature
|
49
|
+
deadline = java.util.Date.new.time + (interval.to_f * 1000).to_i
|
50
|
+
|
51
|
+
if @timers.contains_key(deadline)
|
52
|
+
@timers.get(deadline) << signature
|
53
|
+
else
|
54
|
+
@timers.put(deadline, [signature])
|
55
|
+
end
|
56
|
+
|
57
|
+
@timer_callbacks[signature] = callback
|
58
|
+
signature
|
59
|
+
end
|
60
|
+
|
61
|
+
def cancel_timer(timer_or_sig)
|
62
|
+
if timer_or_sig.respond_to?(:cancel)
|
63
|
+
timer_or_sig.cancel
|
64
|
+
else
|
65
|
+
@timer_callbacks[timer_or_sig] = false if @timer_callbacks.has_key?(timer_or_sig)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def connect(server, port_or_type=nil, handler=nil, *args, &block)
|
70
|
+
if server.nil? or server =~ %r{\w+://}
|
71
|
+
_connect_zmq(server, port_or_type, handler, *args, &block)
|
72
|
+
else
|
73
|
+
_connect_tcp(server, port_or_type, handler, *args, &block)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def connection_count
|
78
|
+
@channels.size
|
79
|
+
end
|
80
|
+
|
81
|
+
def error_handler(callback = nil, &block)
|
82
|
+
@error_handler = callback || block
|
83
|
+
end
|
84
|
+
|
85
|
+
def next_tick(callback=nil, &block)
|
86
|
+
@next_tick_queue << (callback || block)
|
87
|
+
signal_loopbreak if running?
|
88
|
+
end
|
89
|
+
|
90
|
+
def run(callback=nil, shutdown_hook=nil, &block)
|
91
|
+
@callback = callback || block
|
92
|
+
|
93
|
+
add_shutdown_hook(shutdown_hook) if shutdown_hook
|
94
|
+
|
95
|
+
begin
|
96
|
+
@running = true
|
97
|
+
|
98
|
+
add_timer(0, @callback) if @callback
|
99
|
+
|
100
|
+
@selector = Selector.open
|
101
|
+
@run_reactor = true
|
102
|
+
|
103
|
+
while @run_reactor
|
104
|
+
run_deferred_callbacks
|
105
|
+
break unless @run_reactor
|
106
|
+
run_timers
|
107
|
+
break unless @run_reactor
|
108
|
+
remove_unbound_channels
|
109
|
+
check_io
|
110
|
+
add_new_channels
|
111
|
+
process_io
|
112
|
+
end
|
113
|
+
ensure
|
114
|
+
@selector.close rescue nil
|
115
|
+
@selector = nil
|
116
|
+
@unbound_channels += @channels
|
117
|
+
remove_unbound_channels
|
118
|
+
@shutdown_hooks.pop.call until @shutdown_hooks.empty?
|
119
|
+
@next_tick_queue = ConcurrentLinkedQueue.new
|
120
|
+
@running = false
|
121
|
+
ZMachine.context.destroy
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def reactor_running?
|
126
|
+
@running || false
|
127
|
+
end
|
128
|
+
|
129
|
+
def start_server(server, port_or_type=nil, handler=nil, *args, &block)
|
130
|
+
if server =~ %r{\w+://}
|
131
|
+
_bind_zmq(server, port_or_type, handler, *args, &block)
|
132
|
+
else
|
133
|
+
_bind_tcp(server, port_or_type, handler, *args, &block)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def stop_event_loop
|
138
|
+
@run_reactor = false
|
139
|
+
signal_loopbreak
|
140
|
+
end
|
141
|
+
|
142
|
+
def stop_server(channel)
|
143
|
+
channel.close
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def _bind_tcp(address, port, handler, *args, &block)
|
149
|
+
klass = _klass_from_handler(Connection, handler)
|
150
|
+
channel = TCPChannel.new(@selector)
|
151
|
+
channel.bind(address, port)
|
152
|
+
add_channel(channel, Acceptor, klass, *args, &block)
|
153
|
+
end
|
154
|
+
|
155
|
+
def _bind_zmq(address, type, handler, *args, &block)
|
156
|
+
klass = _klass_from_handler(Connection, handler)
|
157
|
+
channel = ZMQChannel.new(type, @selector)
|
158
|
+
channel.bind(address)
|
159
|
+
add_channel(channel, klass, *args, &block)
|
160
|
+
end
|
161
|
+
|
162
|
+
def _connect_tcp(address, port, handler=nil, *args, &block)
|
163
|
+
klass = _klass_from_handler(Connection, handler)
|
164
|
+
channel = TCPChannel.new(@selector)
|
165
|
+
channel.connect(address, port)
|
166
|
+
channel.connect_pending = true
|
167
|
+
add_channel(channel, klass, *args, &block)
|
168
|
+
end
|
169
|
+
|
170
|
+
def _connect_zmq(address, type, handler=nil, *args, &block)
|
171
|
+
klass = _klass_from_handler(Connection, handler)
|
172
|
+
channel = ZMQChannel.new(type, @selector)
|
173
|
+
add_channel(channel, klass, *args, &block)
|
174
|
+
channel.connect(address)
|
175
|
+
channel.handler.connection_completed
|
176
|
+
channel
|
177
|
+
end
|
178
|
+
|
179
|
+
def check_io
|
180
|
+
if @new_channels.size > 0
|
181
|
+
timeout = -1
|
182
|
+
elsif !@timers.empty?
|
183
|
+
now = java.util.Date.new.time
|
184
|
+
timer_key = @timers.first_key
|
185
|
+
timeout = timer_key - now
|
186
|
+
timeout = -1 if timeout <= 0
|
187
|
+
else
|
188
|
+
timeout = 0
|
189
|
+
end
|
190
|
+
|
191
|
+
if @channels.any?(&:has_more?)
|
192
|
+
timeout = -1
|
193
|
+
end
|
194
|
+
|
195
|
+
if timeout == -1
|
196
|
+
@selector.select_now
|
197
|
+
else
|
198
|
+
@selector.select(timeout)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def process_io
|
203
|
+
it = @selector.selected_keys.iterator
|
204
|
+
|
205
|
+
while it.has_next
|
206
|
+
selected_key = it.next
|
207
|
+
it.remove
|
208
|
+
|
209
|
+
if selected_key.connectable?
|
210
|
+
is_connectable(selected_key.attachment)
|
211
|
+
elsif selected_key.acceptable?
|
212
|
+
is_acceptable(selected_key.attachment)
|
213
|
+
else
|
214
|
+
is_writable(selected_key.attachment) if selected_key.writable?
|
215
|
+
is_readable(selected_key.attachment) if selected_key.readable?
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
@channels.each do |channel|
|
220
|
+
is_readable(channel) if channel.has_more?
|
221
|
+
is_writable(channel) if channel.can_send?
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def is_acceptable(channel)
|
226
|
+
client_channel = channel.accept(next_signature)
|
227
|
+
acceptor = channel.handler
|
228
|
+
add_channel(client_channel, acceptor.klass, *acceptor.args, &acceptor.callback)
|
229
|
+
rescue IOException
|
230
|
+
channel.close
|
231
|
+
end
|
232
|
+
|
233
|
+
def is_readable(channel)
|
234
|
+
data = channel.read_inbound_data(@read_buffer)
|
235
|
+
channel.handler.receive_data(data) if data
|
236
|
+
rescue IOException
|
237
|
+
@unbound_channels << channel
|
238
|
+
end
|
239
|
+
|
240
|
+
def is_writable(channel)
|
241
|
+
@unbound_channels << channel unless channel.write_outbound_data
|
242
|
+
rescue IOException
|
243
|
+
@unbound_channels << channel
|
244
|
+
end
|
245
|
+
|
246
|
+
def is_connectable(channel)
|
247
|
+
channel.finish_connecting
|
248
|
+
channel.handler.connection_completed
|
249
|
+
rescue IOException
|
250
|
+
@unbound_channels << channel
|
251
|
+
end
|
252
|
+
|
253
|
+
def add_channel(channel, klass, *args, &block)
|
254
|
+
channel.handler = klass.new(channel, *args)
|
255
|
+
@new_channels << channel
|
256
|
+
block.call(channel.handler) if block
|
257
|
+
channel
|
258
|
+
end
|
259
|
+
|
260
|
+
def add_new_channels
|
261
|
+
@new_channels.each do |channel|
|
262
|
+
begin
|
263
|
+
channel.register
|
264
|
+
@channels << channel
|
265
|
+
rescue ClosedChannelException
|
266
|
+
@unbound_channels << channel
|
267
|
+
end
|
268
|
+
end
|
269
|
+
@new_channels.clear
|
270
|
+
end
|
271
|
+
|
272
|
+
def remove_unbound_channels
|
273
|
+
@unbound_channels.each do |channel|
|
274
|
+
channel.handler.unbind if channel.handler
|
275
|
+
channel.close
|
276
|
+
end
|
277
|
+
@unbound_channels.clear
|
278
|
+
end
|
279
|
+
|
280
|
+
def run_deferred_callbacks
|
281
|
+
# max is current size
|
282
|
+
size = @next_tick_queue.size
|
283
|
+
while size > 0 && callback = @next_tick_queue.poll
|
284
|
+
begin
|
285
|
+
size -= 1
|
286
|
+
callback.call
|
287
|
+
ensure
|
288
|
+
ZMachine.next_tick {} if $!
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def run_timers
|
294
|
+
now = java.util.Date.new.time
|
295
|
+
until @timers.empty?
|
296
|
+
timer_key = @timers.first_key
|
297
|
+
break if timer_key > now
|
298
|
+
|
299
|
+
signatures = @timers.get(timer_key)
|
300
|
+
@timers.remove(timer_key)
|
301
|
+
|
302
|
+
# Fire all timers at this timestamp
|
303
|
+
signatures.each do |signature|
|
304
|
+
callback = @timer_callbacks.delete(signature)
|
305
|
+
return if callback == false # callback cancelled
|
306
|
+
callback or raise UnknownTimerFired, "callback data: #{signature}"
|
307
|
+
callback.call
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def signal_loopbreak
|
313
|
+
@selector.wakeup if @selector
|
314
|
+
end
|
315
|
+
|
316
|
+
def next_signature
|
317
|
+
@next_signature += 1
|
318
|
+
end
|
319
|
+
|
320
|
+
def _klass_from_handler(klass = Connection, handler = nil)
|
321
|
+
if handler and handler.is_a?(Class)
|
322
|
+
handler
|
323
|
+
elsif handler
|
324
|
+
_handler_from_klass(klass, handler)
|
325
|
+
else
|
326
|
+
klass
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def _handler_from_module(klass, handler)
|
331
|
+
handler::CONNECTION_CLASS
|
332
|
+
rescue NameError
|
333
|
+
handler::const_set(:CONNECTION_CLASS, Class.new(klass) { include handler })
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|
337
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
java_import java.nio.channels.ServerSocketChannel
|
2
|
+
|
3
|
+
require 'zmachine/channel'
|
4
|
+
|
5
|
+
module ZMachine
|
6
|
+
class TCPChannel < Channel
|
7
|
+
|
8
|
+
attr_reader :connect_pending
|
9
|
+
|
10
|
+
def initialize(selector)
|
11
|
+
super(selector)
|
12
|
+
@close_scheduled = false
|
13
|
+
@connect_pending = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def register
|
17
|
+
@channel_key ||= @socket.register(@selector, current_events, self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def bind(address, port)
|
21
|
+
address = InetSocketAddress.new(address, port)
|
22
|
+
@socket = ServerSocketChannel.open
|
23
|
+
@socket.configure_blocking(false)
|
24
|
+
@socket.bind(address)
|
25
|
+
end
|
26
|
+
|
27
|
+
def accept
|
28
|
+
client_socket = socket.accept
|
29
|
+
return unless client_socket
|
30
|
+
client_socket.configure_blocking(false)
|
31
|
+
TCPChannel.new(client_socket, @selector)
|
32
|
+
end
|
33
|
+
|
34
|
+
def connect(address, port)
|
35
|
+
address = InetSocketAddress.new(address, port)
|
36
|
+
@socket = SocketChannel.open
|
37
|
+
@socket.configure_blocking(false)
|
38
|
+
|
39
|
+
if socket.connect(address)
|
40
|
+
# Connection returned immediately. Can happen with localhost
|
41
|
+
# connections.
|
42
|
+
# WARNING, this code is untested due to lack of available test
|
43
|
+
# conditions. Ought to be be able to come here from a localhost
|
44
|
+
# connection, but that doesn't happen on Linux. (Maybe on FreeBSD?)
|
45
|
+
# The reason for not handling this until we can test it is that we
|
46
|
+
# really need to return from this function WITHOUT triggering any EM
|
47
|
+
# events. That's because until the user code has seen the signature
|
48
|
+
# we generated here, it won't be able to properly dispatch them. The
|
49
|
+
# C++ EM deals with this by setting pending mode as a flag in ALL
|
50
|
+
# eventable descriptors and making the descriptor select for
|
51
|
+
# writable. Then, it can send UNBOUND and CONNECTION_COMPLETED on the
|
52
|
+
# next pass through the loop, because writable will fire.
|
53
|
+
raise RuntimeError.new("immediate-connect unimplemented")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def close
|
58
|
+
if @channel_key
|
59
|
+
@channel_key.cancel
|
60
|
+
@channel_key = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
@socket.close rescue nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def send_data(data)
|
67
|
+
return if @close_scheduled
|
68
|
+
buffer = ByteBuffer.wrap(data.to_java_bytes)
|
69
|
+
if buffer.remaining() > 0
|
70
|
+
@outbound_queue << buffer
|
71
|
+
update_events
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def read_inbound_data(buffer)
|
76
|
+
buffer.clear
|
77
|
+
raise IOException.new("eof") if @socket.read(buffer) == -1
|
78
|
+
buffer.flip
|
79
|
+
return if buffer.limit == 0
|
80
|
+
String.from_java_bytes(buffer.array[buffer.position...buffer.limit])
|
81
|
+
end
|
82
|
+
|
83
|
+
def write_outbound_data
|
84
|
+
until @outbound_queue.empty?
|
85
|
+
buffer = @outbound_queue.first
|
86
|
+
@socket.write(buffer) if buffer.remaining > 0
|
87
|
+
|
88
|
+
# Did we consume the whole outbound buffer? If yes,
|
89
|
+
# pop it off and keep looping. If no, the outbound network
|
90
|
+
# buffers are full, so break out of here.
|
91
|
+
if buffer.remaining == 0
|
92
|
+
@outbound_queue.shift
|
93
|
+
else
|
94
|
+
break
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
if @outbound_queue.empty? && !@close_scheduled
|
99
|
+
update_events
|
100
|
+
end
|
101
|
+
|
102
|
+
return (@close_scheduled && @outbound_queue.empty?) ? false : true
|
103
|
+
end
|
104
|
+
|
105
|
+
def finish_connecting
|
106
|
+
@socket.finish_connect
|
107
|
+
@connect_pending = false
|
108
|
+
update_events
|
109
|
+
return true
|
110
|
+
end
|
111
|
+
|
112
|
+
def schedule_close(after_writing)
|
113
|
+
@outbound_queue.clear unless after_writing
|
114
|
+
|
115
|
+
if @outbound_queue.empty?
|
116
|
+
return true
|
117
|
+
else
|
118
|
+
update_events
|
119
|
+
@close_scheduled = true
|
120
|
+
return false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# TODO: fix these
|
125
|
+
def peer_name
|
126
|
+
sock = @socket.socket
|
127
|
+
[sock.port, sock.inet_address.host_address]
|
128
|
+
end
|
129
|
+
|
130
|
+
def sock_name
|
131
|
+
sock = @socket.socket
|
132
|
+
[sock.local_port, sock.local_address.host_address]
|
133
|
+
end
|
134
|
+
|
135
|
+
def connect_pending=(value)
|
136
|
+
@connect_pending = value
|
137
|
+
update_events
|
138
|
+
end
|
139
|
+
|
140
|
+
def update_events
|
141
|
+
return unless @channel_key
|
142
|
+
events = current_events
|
143
|
+
if @channel_key.interest_ops != events
|
144
|
+
@channel_key.interest_ops(events)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def has_more?
|
149
|
+
false
|
150
|
+
end
|
151
|
+
|
152
|
+
def current_events
|
153
|
+
if @socket.respond_to?(:accept)
|
154
|
+
return SelectionKey::OP_ACCEPT
|
155
|
+
end
|
156
|
+
|
157
|
+
events = 0
|
158
|
+
|
159
|
+
if @connect_pending
|
160
|
+
events |= SelectionKey::OP_CONNECT
|
161
|
+
else
|
162
|
+
events |= SelectionKey::OP_READ
|
163
|
+
events |= SelectionKey::OP_WRITE unless @outbound_queue.empty?
|
164
|
+
end
|
165
|
+
|
166
|
+
return events
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module ZMachine
|
2
|
+
# Creates a one-time timer
|
3
|
+
#
|
4
|
+
# timer = ZMachine::Timer.new(5) do
|
5
|
+
# # this will never fire because we cancel it
|
6
|
+
# end
|
7
|
+
# timer.cancel
|
8
|
+
#
|
9
|
+
class Timer
|
10
|
+
# Create a new timer that fires after a given number of seconds
|
11
|
+
def initialize(interval, callback=nil, &block)
|
12
|
+
@signature = ZMachine.add_timer(interval, callback || block)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Cancel the timer
|
16
|
+
def cancel
|
17
|
+
ZMachine.cancel_timer(@signature)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Creates a periodic timer
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# n = 0
|
25
|
+
# timer = ZMachine::PeriodicTimer.new(5) do
|
26
|
+
# puts "the time is #{Time.now}"
|
27
|
+
# timer.cancel if (n+=1) > 5
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
class PeriodicTimer
|
31
|
+
# Create a new periodic timer that executes every interval seconds
|
32
|
+
def initialize(interval, callback=nil, &block)
|
33
|
+
@interval = interval
|
34
|
+
@code = callback || block
|
35
|
+
@cancelled = false
|
36
|
+
@work = method(:fire)
|
37
|
+
schedule
|
38
|
+
end
|
39
|
+
|
40
|
+
# Cancel the periodic timer
|
41
|
+
def cancel
|
42
|
+
@cancelled = true
|
43
|
+
end
|
44
|
+
|
45
|
+
# Fire the timer every interval seconds
|
46
|
+
attr_accessor :interval
|
47
|
+
|
48
|
+
# @private
|
49
|
+
def schedule
|
50
|
+
ZMachine.add_timer(@interval, @work)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @private
|
54
|
+
def fire
|
55
|
+
unless @cancelled
|
56
|
+
@code.call
|
57
|
+
schedule
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
java_import org.zeromq.ZMQ
|
2
|
+
java_import org.zeromq.ZMsg
|
3
|
+
java_import org.zeromq.ZMQException
|
4
|
+
|
5
|
+
require 'zmachine/channel'
|
6
|
+
|
7
|
+
module ZMachine
|
8
|
+
class ZMQChannel < Channel
|
9
|
+
|
10
|
+
attr_reader :port
|
11
|
+
|
12
|
+
def initialize(type, selector)
|
13
|
+
super(selector)
|
14
|
+
@socket = ZMachine.context.create_socket(type)
|
15
|
+
@socket.linger = 0
|
16
|
+
@socket.set_router_mandatory(true) if type == ZMQ::ROUTER
|
17
|
+
end
|
18
|
+
|
19
|
+
def register
|
20
|
+
@channel_key ||= @socket.fd.register(@selector, current_events, self)
|
21
|
+
end
|
22
|
+
|
23
|
+
def bind(address)
|
24
|
+
@port = @socket.bind(address)
|
25
|
+
end
|
26
|
+
|
27
|
+
def connect(address)
|
28
|
+
@socket.connect(address) if address
|
29
|
+
end
|
30
|
+
|
31
|
+
def identity=(value)
|
32
|
+
@socket.identity = value.to_java_bytes
|
33
|
+
end
|
34
|
+
|
35
|
+
def close
|
36
|
+
if @channel_key
|
37
|
+
@channel_key.cancel
|
38
|
+
@channel_key = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
@socket.close
|
42
|
+
end
|
43
|
+
|
44
|
+
def send_data(data)
|
45
|
+
@outbound_queue << data unless send_msg(data)
|
46
|
+
end
|
47
|
+
|
48
|
+
def send_msg(msg)
|
49
|
+
msg.java_send(:send, [org.zeromq.ZMQ::Socket], @socket)
|
50
|
+
return true
|
51
|
+
rescue ZMQException
|
52
|
+
return false
|
53
|
+
end
|
54
|
+
|
55
|
+
def read_inbound_data(buffer)
|
56
|
+
return unless has_more?
|
57
|
+
ZMsg.recv_msg(@socket)
|
58
|
+
end
|
59
|
+
|
60
|
+
def write_outbound_data
|
61
|
+
until @outbound_queue.empty?
|
62
|
+
data = @outbound_queue.first
|
63
|
+
if send_msg(data)
|
64
|
+
@outbound_queue.shift
|
65
|
+
else
|
66
|
+
break
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
return true
|
71
|
+
end
|
72
|
+
|
73
|
+
def has_more?
|
74
|
+
@socket.events & ZMQ::Poller::POLLIN == ZMQ::Poller::POLLIN
|
75
|
+
end
|
76
|
+
|
77
|
+
def can_send?
|
78
|
+
@socket.events & ZMQ::Poller::POLLOUT == ZMQ::Poller::POLLOUT
|
79
|
+
end
|
80
|
+
|
81
|
+
def schedule_close(after_writing)
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
# TODO: fix me
|
86
|
+
def peer_name
|
87
|
+
sock = @socket.socket
|
88
|
+
[sock.port, sock.inet_address.host_address]
|
89
|
+
end
|
90
|
+
|
91
|
+
def sock_name
|
92
|
+
sock = @socket.socket
|
93
|
+
[sock.local_port, sock.local_address.host_address]
|
94
|
+
end
|
95
|
+
|
96
|
+
def current_events
|
97
|
+
SelectionKey::OP_READ
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|