zmachine 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|