zmqmachine 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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}]"
|