zmqmachine 0.3.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/.bnsignore +20 -0
- data/History.txt +21 -0
- data/README.rdoc +85 -0
- data/Rakefile +21 -0
- data/examples/fake_ftp.rb +242 -0
- data/examples/one_handed_ping_pong.rb +74 -0
- data/examples/ping_pong.rb +86 -0
- data/examples/pub_sub.rb +195 -0
- data/examples/throttled_ping_pong.rb +103 -0
- data/lib/zm/address.rb +70 -0
- data/lib/zm/deferrable.rb +209 -0
- data/lib/zm/exceptions.rb +45 -0
- data/lib/zm/message.rb +52 -0
- data/lib/zm/reactor.rb +458 -0
- data/lib/zm/sockets/base.rb +216 -0
- data/lib/zm/sockets/pair.rb +87 -0
- data/lib/zm/sockets/pub.rb +80 -0
- data/lib/zm/sockets/rep.rb +96 -0
- data/lib/zm/sockets/req.rb +96 -0
- data/lib/zm/sockets/sub.rb +83 -0
- data/lib/zm/sockets/xrep.rb +86 -0
- data/lib/zm/sockets/xreq.rb +86 -0
- data/lib/zm/sockets.rb +4 -0
- data/lib/zm/timers.rb +220 -0
- data/lib/zmqmachine.rb +76 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/zmqmachine_spec.rb +6 -0
- data/version.txt +1 -0
- data/zmqmachine.gemspec +48 -0
- metadata +133 -0
data/.bnsignore
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# The list of files that should be ignored by Mr Bones.
|
2
|
+
# Lines that start with '#' are comments.
|
3
|
+
#
|
4
|
+
# A .gitignore file can be used instead by setting it as the ignore
|
5
|
+
# file in your Rakefile:
|
6
|
+
#
|
7
|
+
# Bones {
|
8
|
+
# ignore_file '.gitignore'
|
9
|
+
# }
|
10
|
+
#
|
11
|
+
# For a project with a C extension, the following would be a good set of
|
12
|
+
# exclude patterns (uncomment them if you want to use them):
|
13
|
+
# *.[oa]
|
14
|
+
# *~
|
15
|
+
announcement.txt
|
16
|
+
coverage
|
17
|
+
doc
|
18
|
+
pkg
|
19
|
+
examples/work
|
20
|
+
.DS_Store
|
data/History.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
== 0.3.0 / 2010-08-15
|
2
|
+
* Added XREQ/XREP socket types
|
3
|
+
* Moved the time functions for ZM::Timers into a pair of
|
4
|
+
class methods. Instead of accessing Time directly, it should
|
5
|
+
be accessed via ZM::Timers.now and ZM::Timers.now_converted.
|
6
|
+
Doing so allows the programmer to reopen the ZM::Timers class and
|
7
|
+
redefine how time is procured if necessary (e.g. every second of
|
8
|
+
wall clock time should translate to 1 minute of time inside the
|
9
|
+
reactor).
|
10
|
+
* Still needs specs!
|
11
|
+
|
12
|
+
== 0.2.0 / 2010-06-06
|
13
|
+
* Updated internals to conform to the 0mq api as of
|
14
|
+
release 2.0.7.
|
15
|
+
|
16
|
+
* Minor api enhancements.
|
17
|
+
|
18
|
+
== 0.1.0 / 2010-06-02
|
19
|
+
|
20
|
+
* 1 major enhancement
|
21
|
+
* Birthday!
|
data/README.rdoc
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
zmqmachine
|
2
|
+
by Chuck Remes
|
3
|
+
http://github.com/chuckremes/zmqmachine
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
ZMQMachine is another Ruby implementation of the reactor pattern but this
|
8
|
+
time using 0mq sockets rather than POSIX sockets.
|
9
|
+
|
10
|
+
Unlike the great Eventmachine ruby project and the Python Twisted
|
11
|
+
project which work with POSIX sockets, ZMQMachine is inherently threaded. The
|
12
|
+
0mq sockets backing the reactor use a thread pool for performing
|
13
|
+
their work so already it is different from most other reactors. Also, a
|
14
|
+
single program may create multiple reactor instances which runs in
|
15
|
+
its own thread. All activity within the reactor is single-threaded
|
16
|
+
and asynchronous.
|
17
|
+
|
18
|
+
It is possible to extend the 0mq library to "poll" normal file
|
19
|
+
descriptors. This isn't on my roadmap but patches are accepted.
|
20
|
+
|
21
|
+
== FEATURES/PROBLEMS:
|
22
|
+
|
23
|
+
* No specs yet.
|
24
|
+
|
25
|
+
* Documentation is limited. I need to write up a lot more detail on the Handler classes passed
|
26
|
+
to socket instances and how this differs from the Eventmachine way.
|
27
|
+
|
28
|
+
* Exceptions and error codes haven't really been implemented yet.
|
29
|
+
|
30
|
+
* Some classes are just skeletons.
|
31
|
+
|
32
|
+
* Recommended for JRuby since it is the only existing runtime that uses
|
33
|
+
native threads without a GIL. MRI 1.9.x is okay but may have
|
34
|
+
threading problems.
|
35
|
+
|
36
|
+
== SYNOPSIS:
|
37
|
+
|
38
|
+
Read and execute the examples in the examples directory.
|
39
|
+
|
40
|
+
== REQUIREMENTS:
|
41
|
+
|
42
|
+
Requires the 0mq library
|
43
|
+
|
44
|
+
* 0mq 2.0.7
|
45
|
+
|
46
|
+
Depends on 2 external gems.
|
47
|
+
|
48
|
+
* ffi-rzmq (>= 0.5.0)
|
49
|
+
* ffi (> 0.6.3) (built from master for threading fixes)
|
50
|
+
|
51
|
+
== INSTALL:
|
52
|
+
|
53
|
+
Make sure the 0MQ library is already installed on your system. Secondly,
|
54
|
+
make sure the FFI gem is built from its project master (see Requirements).
|
55
|
+
Lastly, verify the ffi-rzmq gem is built from its project master.
|
56
|
+
|
57
|
+
% git clone github.com/chuckremes/zmqmachine.git
|
58
|
+
% cd zmqmachine
|
59
|
+
% gem build zmqmachine.gemspec
|
60
|
+
% gem install zmqmachine-*.gem
|
61
|
+
|
62
|
+
== LICENSE:
|
63
|
+
|
64
|
+
(The MIT License)
|
65
|
+
|
66
|
+
Copyright (c) 2010 Chuck Remes
|
67
|
+
|
68
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
69
|
+
a copy of this software and associated documentation files (the
|
70
|
+
'Software'), to deal in the Software without restriction, including
|
71
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
72
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
73
|
+
permit persons to whom the Software is furnished to do so, subject to
|
74
|
+
the following conditions:
|
75
|
+
|
76
|
+
The above copyright notice and this permission notice shall be
|
77
|
+
included in all copies or substantial portions of the Software.
|
78
|
+
|
79
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
80
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
81
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
82
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
83
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
84
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
85
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
begin
|
3
|
+
require 'bones'
|
4
|
+
rescue LoadError
|
5
|
+
abort '### Please install the "bones" gem ###'
|
6
|
+
end
|
7
|
+
|
8
|
+
task :default => 'test:run'
|
9
|
+
task 'gem:release' => 'test:run'
|
10
|
+
|
11
|
+
Bones {
|
12
|
+
name 'zmqmachine'
|
13
|
+
authors 'Chuck Remes'
|
14
|
+
email 'cremes@mac.com'
|
15
|
+
url 'http://github.com/chuckremes/zmqmachine'
|
16
|
+
ignore_file '.gitignore'
|
17
|
+
readme_file 'README.rdoc'
|
18
|
+
|
19
|
+
depend_on 'ffi-rzmq', '>= 0.5.0'
|
20
|
+
}
|
21
|
+
|
@@ -0,0 +1,242 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'ffi-rzmq'
|
4
|
+
require 'json'
|
5
|
+
require '../lib/zmqmachine'
|
6
|
+
|
7
|
+
# This example shows how to pair up REQ/REP sockets with PUB/SUB
|
8
|
+
# sockets for doing an RPC-style request with an out-of-band
|
9
|
+
# response. The RPC reply contains information about the out-of-
|
10
|
+
# band data so the servers know that the transfer was complete.
|
11
|
+
#
|
12
|
+
# This is NOT an implementation of a real FTP server; I used
|
13
|
+
# the acronym to highlight the role of each component.
|
14
|
+
#
|
15
|
+
|
16
|
+
|
17
|
+
server_address = ZM::Address.new '127.0.0.1', 6555, :tcp
|
18
|
+
|
19
|
+
|
20
|
+
class FTPDataClient
|
21
|
+
include ZM::Deferrable
|
22
|
+
|
23
|
+
def initialize context, address, topic, process
|
24
|
+
@context = context
|
25
|
+
@address = address
|
26
|
+
@topic = topic
|
27
|
+
@process = process
|
28
|
+
@received_count = 0
|
29
|
+
@expected_count = -1
|
30
|
+
|
31
|
+
callback do
|
32
|
+
puts "FTPDataClient#callback fired, received all file chunks"
|
33
|
+
@context.next_tick { @context.stop }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def on_attach socket
|
38
|
+
@socket = socket
|
39
|
+
@socket.bind @address
|
40
|
+
@socket.subscribe @topic
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_readable socket, messages
|
44
|
+
obj = decode strip_topic(messages.first.copy_out_string)
|
45
|
+
puts "#{self.class.name}#on_readable: process obj #{obj.inspect}"
|
46
|
+
@received_count += @process.call(obj)
|
47
|
+
succeed if complete?
|
48
|
+
end
|
49
|
+
|
50
|
+
def on_writable(socket); puts "#{self.class.name}#on_writable, should never be called"; end
|
51
|
+
|
52
|
+
def should_expect count
|
53
|
+
puts "#{self.class.name}#should_expect, set to expect [#{count}], already received [#{@received_count}]"
|
54
|
+
@expected_count = count
|
55
|
+
succeed if complete?
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def strip_topic payload
|
62
|
+
payload.split('|').last
|
63
|
+
end
|
64
|
+
|
65
|
+
def complete?
|
66
|
+
@expected_count > 0 && @expected_count == @received_count
|
67
|
+
end
|
68
|
+
|
69
|
+
def decode string
|
70
|
+
JSON.parse string
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class FTPControlClient
|
75
|
+
def initialize context, address
|
76
|
+
@context = context
|
77
|
+
@address = address
|
78
|
+
@received_count = 0
|
79
|
+
end
|
80
|
+
|
81
|
+
def on_attach socket
|
82
|
+
@socket = socket
|
83
|
+
rc = socket.connect @address
|
84
|
+
end
|
85
|
+
|
86
|
+
def on_readable socket, messages
|
87
|
+
reply = decode messages.first.copy_out_string
|
88
|
+
puts "#{self.class.name}#on_readable: reply from FTPDataServer #{reply.inspect}"
|
89
|
+
|
90
|
+
@data_client.should_expect reply['total_chunks']
|
91
|
+
end
|
92
|
+
|
93
|
+
def get filename, &blk
|
94
|
+
sub_address = ZM::Address.new @address.host, 5556, @address.transport
|
95
|
+
topic = "file.#{filename}|"
|
96
|
+
|
97
|
+
@data_client = FTPDataClient.new @context, sub_address, topic, blk
|
98
|
+
@sub_socket = @context.sub_socket @data_client
|
99
|
+
|
100
|
+
req = {'filename' => filename, 'reply_to' => sub_address.to_s}
|
101
|
+
|
102
|
+
puts "#{self.class.name}#get: send request for the file chunks to be published"
|
103
|
+
@socket.send_message_string encode(req)
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def decode string
|
110
|
+
JSON.parse string
|
111
|
+
end
|
112
|
+
|
113
|
+
def encode obj
|
114
|
+
JSON.generate obj
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class FTPDataServer
|
119
|
+
include ZM::Deferrable
|
120
|
+
|
121
|
+
attr_reader :total_count
|
122
|
+
|
123
|
+
def initialize request
|
124
|
+
@request = request
|
125
|
+
@address_string = request['reply_to']
|
126
|
+
@topic = "file.#{request['filename']}|"
|
127
|
+
@total_count = 0
|
128
|
+
end
|
129
|
+
|
130
|
+
def on_attach socket
|
131
|
+
if open_file
|
132
|
+
@socket = socket
|
133
|
+
@socket.connect @address_string
|
134
|
+
else
|
135
|
+
puts "#{self.class.name}#on_attach: @total_count is 0"
|
136
|
+
succeed @total_count
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def on_writable socket
|
141
|
+
if more_file_data?
|
142
|
+
@total_count += 1
|
143
|
+
obj = encode(next_file_chunk)
|
144
|
+
puts "#{self.class.name}#on_writable: send chunk [#{@total_count}], obj #{obj.inspect}"
|
145
|
+
socket.send_message_string obj
|
146
|
+
else
|
147
|
+
puts "#{self.class.name}#on_writable: done [#{@total_count}]"
|
148
|
+
succeed @total_count
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def open_file
|
156
|
+
@chunks = rand(5) + 5
|
157
|
+
@chunks > 0
|
158
|
+
end
|
159
|
+
|
160
|
+
def more_file_data?
|
161
|
+
@chunks > 0
|
162
|
+
end
|
163
|
+
|
164
|
+
def next_file_chunk
|
165
|
+
@chunks -= 1
|
166
|
+
chunk = rand(999_999_999) + 1_000_000_000
|
167
|
+
{'chunk' => chunk}
|
168
|
+
end
|
169
|
+
|
170
|
+
def encode obj
|
171
|
+
"#{@topic}#{JSON.generate(obj)}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class FTPControlServer
|
176
|
+
def initialize context, address
|
177
|
+
@context = context
|
178
|
+
@address = address
|
179
|
+
end
|
180
|
+
|
181
|
+
def on_attach socket
|
182
|
+
@socket = socket
|
183
|
+
rc = socket.bind @address
|
184
|
+
end
|
185
|
+
|
186
|
+
def on_readable socket, messages
|
187
|
+
request = decode messages.first.copy_out_string
|
188
|
+
puts "#{self.class.name}: query #{request.inspect}"
|
189
|
+
@pub_handler = FTPDataServer.new request
|
190
|
+
@pub_socket = @context.pub_socket @pub_handler
|
191
|
+
|
192
|
+
@pub_handler.callback do |total_chunks|
|
193
|
+
puts "FTPDataServer#callback fired, sent all file chunks"
|
194
|
+
|
195
|
+
reply = encode 'total_chunks' => total_chunks
|
196
|
+
socket.send_message_string reply
|
197
|
+
|
198
|
+
# clean up after ourselves
|
199
|
+
@context.close_socket @pub_socket
|
200
|
+
@pub_handler = nil
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def on_writable socket
|
205
|
+
puts "#{self.class.name}#on_writable ERROR, should never be called"
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
def decode string
|
212
|
+
JSON.parse string
|
213
|
+
end
|
214
|
+
|
215
|
+
def encode obj
|
216
|
+
JSON.generate obj
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
# Run both handlers within the same reactor context
|
222
|
+
ctx1 = ZM::Reactor.new(:test).run do |context|
|
223
|
+
|
224
|
+
@request_server = FTPControlServer.new context, server_address
|
225
|
+
context.rep_socket @request_server
|
226
|
+
|
227
|
+
@request_client = FTPControlClient.new context, server_address
|
228
|
+
context.req_socket @request_client
|
229
|
+
|
230
|
+
@request_client.get('fakeo') do |file_chunk|
|
231
|
+
puts "block: file chunk #{file_chunk.inspect}"
|
232
|
+
|
233
|
+
# the number of chunks processed in this block; this return
|
234
|
+
# value is used by FTPDataClient#on_readable for tallying
|
235
|
+
# the total number of processed chunks.
|
236
|
+
1
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
ctx1.join
|
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'ffi-rzmq'
|
4
|
+
require '../lib/zmqmachine'
|
5
|
+
|
6
|
+
# This example illustrates how a single handler can be used
|
7
|
+
# by multiple sockets.
|
8
|
+
#
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
Allowed_pongs = 100_000
|
13
|
+
|
14
|
+
class PingPongHandler
|
15
|
+
attr_reader :sent_count, :received_count
|
16
|
+
|
17
|
+
def initialize context
|
18
|
+
@context = context
|
19
|
+
@sent_count = 0
|
20
|
+
@received_count = 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_attach socket
|
24
|
+
address = ZM::Address.new '127.0.0.1', 5555, :tcp
|
25
|
+
|
26
|
+
case socket.kind
|
27
|
+
when :reply
|
28
|
+
rc = socket.bind address
|
29
|
+
when :request
|
30
|
+
rc = socket.connect address
|
31
|
+
rc = socket.send_message_string "#{'a' * 2048}"
|
32
|
+
@sent_count += 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def on_readable socket, messages
|
37
|
+
@received_count += 1
|
38
|
+
|
39
|
+
case socket.kind
|
40
|
+
when :reply
|
41
|
+
socket.send_message messages.first
|
42
|
+
when :request
|
43
|
+
socket.send_message messages.first
|
44
|
+
end
|
45
|
+
|
46
|
+
@sent_count += 1
|
47
|
+
@context.next_tick { @context.stop } if @sent_count == Allowed_pongs
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
handler = nil
|
52
|
+
# Run both handlers within the same reactor context
|
53
|
+
ctx1 = ZM::Reactor.new(:test).run do |context|
|
54
|
+
handler = PingPongHandler.new context
|
55
|
+
|
56
|
+
context.rep_socket handler
|
57
|
+
|
58
|
+
context.req_socket handler
|
59
|
+
|
60
|
+
start = Time.now
|
61
|
+
timer = context.periodical_timer(2000) do
|
62
|
+
now = Time.now
|
63
|
+
puts "[#{now - start}] seconds have elapsed; it is now [#{now}]"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
ctx1.join
|
68
|
+
#puts "Started at [#{Time.now}]"
|
69
|
+
#puts "main thread will sleep [#{sleep_time}] seconds before aborting the context threads"
|
70
|
+
#sleep sleep_time
|
71
|
+
#
|
72
|
+
#ctx1.stop
|
73
|
+
#ctx2.stop
|
74
|
+
puts "received [#{handler.received_count}], sent [#{handler.sent_count}]"
|
@@ -0,0 +1,86 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'ffi-rzmq'
|
4
|
+
require '../lib/zmqmachine'
|
5
|
+
|
6
|
+
# This example illustrates how to write a simple set of
|
7
|
+
# handlers for providing message ping-pong using
|
8
|
+
# a REQ/REP socket pair. All activity is asynchronous and
|
9
|
+
# relies on non-blocking I/O.
|
10
|
+
|
11
|
+
|
12
|
+
Allowed_pongs = 100_000
|
13
|
+
|
14
|
+
|
15
|
+
class PongHandler
|
16
|
+
attr_reader :sent_count, :received_count
|
17
|
+
|
18
|
+
def initialize context
|
19
|
+
@context = context
|
20
|
+
@sent_count = 0
|
21
|
+
@received_count = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_attach socket
|
25
|
+
address = ZM::Address.new '127.0.0.1', 5555, :tcp
|
26
|
+
rc = socket.bind address
|
27
|
+
end
|
28
|
+
|
29
|
+
def on_readable socket, messages
|
30
|
+
@received_count += 1
|
31
|
+
socket.send_message messages.first
|
32
|
+
@sent_count += 1
|
33
|
+
|
34
|
+
@context.next_tick { @context.stop } if @sent_count == Allowed_pongs
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class PingHandler
|
39
|
+
attr_reader :sent_count, :received_count
|
40
|
+
|
41
|
+
def initialize context
|
42
|
+
@context = context
|
43
|
+
@sent_count = 0
|
44
|
+
@received_count = 0
|
45
|
+
end
|
46
|
+
|
47
|
+
def on_attach socket
|
48
|
+
address = ZM::Address.new '127.0.0.1', 5555, :tcp
|
49
|
+
rc = socket.connect address
|
50
|
+
rc = socket.send_message_string "#{'a' * 2048}"
|
51
|
+
@sent_count += 1
|
52
|
+
end
|
53
|
+
|
54
|
+
def on_readable socket, messages
|
55
|
+
@received_count += 1
|
56
|
+
rc = socket.send_message messages.first
|
57
|
+
@sent_count += 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Run both handlers within the same reactor context
|
62
|
+
ctx1 = ZM::Reactor.new(:test).run do |context|
|
63
|
+
@pong_handler = PongHandler.new context
|
64
|
+
context.rep_socket @pong_handler
|
65
|
+
|
66
|
+
@ping_handler = PingHandler.new context
|
67
|
+
context.req_socket @ping_handler
|
68
|
+
|
69
|
+
start = Time.now
|
70
|
+
timer = context.periodical_timer(2000) do
|
71
|
+
now = Time.now
|
72
|
+
puts "[#{now - start}] seconds have elapsed; it is now [#{now}]"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Or, run each handler in separate contexts each with its
|
77
|
+
# own thread.
|
78
|
+
#ctx2 = ZM::Reactor.new(:test).run do |context|
|
79
|
+
# @ping_handler = PingHandler.new context
|
80
|
+
# context.req_socket @ping_handler
|
81
|
+
#end
|
82
|
+
|
83
|
+
|
84
|
+
ctx1.join
|
85
|
+
#ctx2.join
|
86
|
+
puts "received [#{@pong_handler.received_count}], sent [#{@pong_handler.sent_count}]"
|