cztop 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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +31 -0
  5. data/.yardopts +1 -0
  6. data/AUTHORS +1 -0
  7. data/CHANGES.md +3 -0
  8. data/Gemfile +10 -0
  9. data/Guardfile +61 -0
  10. data/LICENSE +5 -0
  11. data/Procfile +3 -0
  12. data/README.md +408 -0
  13. data/Rakefile +6 -0
  14. data/bin/console +7 -0
  15. data/bin/setup +7 -0
  16. data/ci-scripts/install-deps +9 -0
  17. data/cztop.gemspec +36 -0
  18. data/examples/ruby_actor/actor.rb +100 -0
  19. data/examples/simple_req_rep/rep.rb +12 -0
  20. data/examples/simple_req_rep/req.rb +35 -0
  21. data/examples/taxi_system/.gitignore +2 -0
  22. data/examples/taxi_system/Makefile +2 -0
  23. data/examples/taxi_system/README.gsl +115 -0
  24. data/examples/taxi_system/README.md +276 -0
  25. data/examples/taxi_system/broker.rb +98 -0
  26. data/examples/taxi_system/client.rb +34 -0
  27. data/examples/taxi_system/generate_keys.rb +24 -0
  28. data/examples/taxi_system/start_broker.sh +2 -0
  29. data/examples/taxi_system/start_clients.sh +11 -0
  30. data/lib/cztop/actor.rb +308 -0
  31. data/lib/cztop/authenticator.rb +97 -0
  32. data/lib/cztop/beacon.rb +96 -0
  33. data/lib/cztop/certificate.rb +176 -0
  34. data/lib/cztop/config/comments.rb +66 -0
  35. data/lib/cztop/config/serialization.rb +82 -0
  36. data/lib/cztop/config/traversing.rb +157 -0
  37. data/lib/cztop/config.rb +119 -0
  38. data/lib/cztop/frame.rb +158 -0
  39. data/lib/cztop/has_ffi_delegate.rb +85 -0
  40. data/lib/cztop/message/frames.rb +74 -0
  41. data/lib/cztop/message.rb +191 -0
  42. data/lib/cztop/monitor.rb +102 -0
  43. data/lib/cztop/poller.rb +334 -0
  44. data/lib/cztop/polymorphic_zsock_methods.rb +24 -0
  45. data/lib/cztop/proxy.rb +149 -0
  46. data/lib/cztop/send_receive_methods.rb +35 -0
  47. data/lib/cztop/socket/types.rb +207 -0
  48. data/lib/cztop/socket.rb +106 -0
  49. data/lib/cztop/version.rb +3 -0
  50. data/lib/cztop/z85.rb +157 -0
  51. data/lib/cztop/zsock_options.rb +334 -0
  52. data/lib/cztop.rb +55 -0
  53. data/perf/README.md +79 -0
  54. data/perf/inproc_lat.rb +49 -0
  55. data/perf/inproc_thru.rb +42 -0
  56. data/perf/local_lat.rb +35 -0
  57. data/perf/remote_lat.rb +26 -0
  58. metadata +297 -0
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../../lib/cztop'
3
+
4
+ ##
5
+ # This example shows how to create a simple actor using a Ruby block.
6
+ # Of course it also supports the CZMQ-native way, passing a pointer to
7
+ # a C function. That's how CZTop::Beacon, CZTop::Authenticator, ... are
8
+ # implemented.
9
+ #
10
+
11
+ counter = 0
12
+ actor = CZTop::Actor.new do |msg, pipe|
13
+ # This block is called once for every received message.
14
+
15
+ case command = msg[0]
16
+ when "UPCASE"
17
+ # Upcase second message frame and send back.
18
+ word = msg[1]
19
+ puts ">>> Actor converts #{word.inspect} to uppercase."
20
+ pipe << word.upcase
21
+ when "COUNT"
22
+ # Count up.
23
+ counter += 1
24
+ puts ">>> Actor has incremented counter to: #{counter}"
25
+ when "PRODUCE"
26
+ # Produce multiple messages.
27
+ num = msg[1].to_i
28
+ puts ">>> Actor produces #{num} messages."
29
+ num.times { |i| pipe << "FOO #{i+1}/#{num}" }
30
+ else
31
+ # crashes actor #=> Actor#dead? and Actor#crashed? will return true
32
+ # also, Actor#exception will return this exception.
33
+ raise "invalid command: #{command}"
34
+ end
35
+ end
36
+ puts ">>> Actor created."
37
+
38
+ ##
39
+ # Let actor count.
40
+ #
41
+ # Actor#<< is thread-safe.
42
+ #
43
+ actor << "COUNT"
44
+ actor << "COUNT"
45
+ actor << "COUNT"
46
+ actor << "COUNT"
47
+ actor << "COUNT"
48
+ actor << "COUNT"
49
+
50
+
51
+ ##
52
+ # Request a response from actor.
53
+ #
54
+ # Actor#request is thread-safe and ensures that the right response gets
55
+ # returned.
56
+ #
57
+ puts actor.request(["UPCASE", "foobar"]).inspect
58
+ #=> #<CZTop::Message:0x7f98c8d96000 frames=1 content_size=6 content=["FOOBAR"]>
59
+
60
+
61
+ ##
62
+ # Let actor produce some messages.
63
+ #
64
+ # Actor#receive is thread-safe, but doesn't guarantee any particular order.
65
+ #
66
+ actor << %w[ PRODUCE 5 ]
67
+ puts actor.receive[0] #1
68
+ puts actor.receive[0] #2
69
+ puts actor.receive[0] #3
70
+ puts actor.receive[0] #4
71
+ puts actor.receive[0] #5
72
+
73
+ ##
74
+ # Let actor die.
75
+ #
76
+ # Blocks until dead.
77
+ actor.terminate
78
+ actor.dead? #=> true
79
+ actor.crashed? #=> false
80
+ actor.exception #=> nil, because it didn't crash
81
+
82
+
83
+ __END__
84
+ Example output:
85
+
86
+ >>> Actor created.
87
+ >>> Actor has incremented counter to: 1
88
+ >>> Actor has incremented counter to: 2
89
+ >>> Actor has incremented counter to: 3
90
+ >>> Actor has incremented counter to: 4
91
+ >>> Actor has incremented counter to: 5
92
+ >>> Actor has incremented counter to: 6
93
+ >>> Actor converts "foobar" to uppercase.
94
+ #<CZTop::Message:0x7f81c55bdca0 frames=1 content_size=6 content=["FOOBAR"]>
95
+ >>> Actor produces 5 messages.
96
+ FOO 1/5
97
+ FOO 2/5
98
+ FOO 3/5
99
+ FOO 4/5
100
+ FOO 5/5
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../../lib/cztop'
3
+
4
+ # create and bind socket
5
+ socket = CZTop::Socket::REP.new("ipc:///tmp/req_rep_example")
6
+ puts "<<< Socket bound to #{socket.last_endpoint.inspect}"
7
+
8
+ # Simply echo every message, with every frame String#upcase'd.
9
+ while msg = socket.receive
10
+ puts "<<< #{msg.to_a.inspect}"
11
+ socket << msg.to_a.map(&:upcase)
12
+ end
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../../lib/cztop'
3
+
4
+ # connect
5
+ socket = CZTop::Socket::REQ.new("ipc:///tmp/req_rep_example")
6
+ puts ">>> Socket connected."
7
+
8
+ # simple string
9
+ socket << "foobar"
10
+ msg = socket.receive
11
+ puts ">>> #{msg.to_a.inspect}"
12
+
13
+ # multi frame message as array
14
+ socket << %w[foo bar baz]
15
+ msg = socket.receive
16
+ puts ">>> #{msg.to_a.inspect}"
17
+
18
+ # manually instantiating a Message
19
+ msg = CZTop::Message.new("bla")
20
+ msg << "another frame" # append a frame
21
+ socket << msg
22
+ msg = socket.receive
23
+ puts ">>> #{msg.to_a.inspect}"
24
+
25
+ ##
26
+ # This will send 20 additional messages:
27
+ #
28
+ # ./req.rb 20
29
+ #
30
+ if ARGV.first
31
+ ARGV.first.to_i.times do
32
+ socket << ["fooooooooo", "baaaaaar"]
33
+ puts ">>> " + socket.receive.to_a.inspect
34
+ end
35
+ end
@@ -0,0 +1,2 @@
1
+ secret_keys/
2
+ public_keys/
@@ -0,0 +1,2 @@
1
+ README.md: README.gsl generate_keys.rb broker.rb client.rb start_broker.sh start_clients.sh
2
+ gsl README
@@ -0,0 +1,115 @@
1
+ .output "README.md"
2
+ .template 1
3
+ # Taxi System
4
+
5
+ Suppose you're running a taxi company. You have a set of taxi drivers
6
+ working for you. You'd like to connect them to your central server, so they're
7
+ ready to get service requests from customers who'd like to get picked up by a
8
+ taxi from some place X. As soon as a customer sends his service request, the
9
+ central server will send the closest taxi nearby that's available to the
10
+ customer.
11
+
12
+ Of course you want the communication between the broker and the taxi drivers to
13
+ be secure, meaning you want encryption and authentication.
14
+
15
+ You also want ping-pong heartbeating, as you want to have confidence you can
16
+ get in touch with your taxi drivers any time you want. And if a service
17
+ request can't be delivered to a particular taxi driver, you wanna know
18
+ immediately.
19
+
20
+ This solution is implemented using CLIENT/SERVER sockets and the CURVE
21
+ security mechanism.
22
+
23
+ ## Broker
24
+
25
+ Here's a possible implementation of the broker. What you'll have to provide
26
+ are the environment variables `BROKER_ADDRESS` (the public TCP endpoint),
27
+ `BROKER_CERT` (path to the broker's secret+public keys), and `CLIENT_CERTS`
28
+ (directory to taxi drivers' certificates, public keys only).
29
+
30
+ After the start, the broker will just start listening for the drivers (CLIENT
31
+ sockets) to connect. After a driver has connected, authenticated, and sent its
32
+ `HELLO` message, the broker answers with a `WELCOME` or `WELCOMEBACK` message,
33
+ depending if the driver was connected before (it might have reconnected and
34
+ been assigned a new routing ID).
35
+
36
+ The broker will present you with a Pry shell. Right before starting the shell,
37
+ there's a small usage information, but it's not very well visible due to Pry's
38
+ noisy start. It's simple, though. Inside that shell, you can use the method
39
+ `#send_command(driver, command)`. Example:
40
+
41
+ ```
42
+ pry> send_command("driver1", "foobar")
43
+ ```
44
+
45
+ Depending on whether the driver is connected, it'll send the message or report
46
+ that it cannot do so.
47
+
48
+ ```ruby
49
+ .literal from "broker.rb"
50
+ ```
51
+
52
+ ## Client
53
+
54
+ Here you have to provide the environment variables `BROKER_ADDRESS` (ditto),
55
+ `BROKER_CERT` (public key only), `CLIENT_CERT` (taxi driver's certificate
56
+ containing the secret+public keys).
57
+
58
+ After connecting to the broker and completing the security handshake, the
59
+ client sends a `HELLO` message, after which it immediately expects some answer
60
+ from the broker (see above). After that, it just listens for messages (service
61
+ requests) and prints them into the terminal.
62
+
63
+ ```ruby
64
+ .literal from "client.rb"
65
+ ```
66
+
67
+ ## How to run the example
68
+
69
+ ### Generate broker's and drivers' keys
70
+
71
+ Here's a simple script that'll create the broker's certificate and the taxi
72
+ drivers' certificates. There are also public key only files so a minimum amount
73
+ of information can be made available on one system, e.g. a taxi driver's system
74
+ must not know the broker's secret key. Also, the broker doesn't necessarily
75
+ need to know the clients' secret keys just to authenticate them.
76
+
77
+ ```ruby
78
+ .literal from "generate_keys.rb"
79
+
80
+ ```
81
+ Run it as follows:
82
+
83
+ ```
84
+ \./generate_keys.rb
85
+ ```
86
+
87
+ ### Start broker
88
+
89
+ Run this:
90
+
91
+ ```
92
+ \./start_broker.sh
93
+ ```
94
+
95
+ which will execute the following script:
96
+
97
+ ```sh
98
+ .literal from "start_broker.sh"
99
+ ```
100
+
101
+ ### Start driver software instances
102
+
103
+ Run this in another terminal:
104
+
105
+ ```
106
+ \./start_clients.sh
107
+ ```
108
+
109
+ which will execute the following script:
110
+
111
+ ```sh
112
+ .literal from "start_clients.sh"
113
+ ```
114
+
115
+ .endtemplate
@@ -0,0 +1,276 @@
1
+ # Taxi System
2
+
3
+ Suppose you're running a taxi company. You have a set of taxi drivers
4
+ working for you. You'd like to connect them to your central server, so they're
5
+ ready to get service requests from customers who'd like to get picked up by a
6
+ taxi from some place X. As soon as a customer sends his service request, the
7
+ central server will send the closest taxi nearby that's available to the
8
+ customer.
9
+
10
+ Of course you want the communication between the broker and the taxi drivers to
11
+ be secure, meaning you want encryption and authentication.
12
+
13
+ You also want ping-pong heartbeating, as you want to have confidence you can
14
+ get in touch with your taxi drivers any time you want. And if a service
15
+ request can't be delivered to a particular taxi driver, you wanna know
16
+ immediately.
17
+
18
+ This solution is implemented using CLIENT/SERVER sockets and the CURVE
19
+ security mechanism.
20
+
21
+ ## Broker
22
+
23
+ Here's a possible implementation of the broker. What you'll have to provide
24
+ are the environment variables `BROKER_ADDRESS` (the public TCP endpoint),
25
+ `BROKER_CERT` (path to the broker's secret+public keys), and `CLIENT_CERTS`
26
+ (directory to taxi drivers' certificates, public keys only).
27
+
28
+ After the start, the broker will just start listening for the drivers (CLIENT
29
+ sockets) to connect. After a driver has connected, authenticated, and sent its
30
+ `HELLO` message, the broker answers with a `WELCOME` or `WELCOMEBACK` message,
31
+ depending if the driver was connected before (it might have reconnected and
32
+ been assigned a new routing ID).
33
+
34
+ The broker will present you with a Pry shell. Right before starting the shell,
35
+ there's a small usage information, but it's not very well visible due to Pry's
36
+ noisy start. It's simple, though. Inside that shell, you can use the method
37
+ `#send_command(driver, command)`. Example:
38
+
39
+ ```
40
+ pry> send_command("driver1", "foobar")
41
+ ```
42
+
43
+ Depending on whether the driver is connected, it'll send the message or report
44
+ that it cannot do so.
45
+
46
+ ```ruby
47
+ #!/usr/bin/env ruby
48
+ require 'pry'
49
+ require 'pathname'
50
+ require_relative '../../lib/cztop'
51
+
52
+ endpoint = ENV["BROKER_ADDRESS"]
53
+ broker_cert = CZTop::Certificate.load ENV["BROKER_CERT"] # secret+public
54
+ client_certs = ENV["CLIENT_CERTS"] # /path/to/client_certs/
55
+ drivers = Pathname.new(client_certs).children.map(&:basename).map(&:to_s)
56
+
57
+ authenticator = CZTop::Authenticator.new
58
+ authenticator.verbose!
59
+ authenticator.curve(client_certs)
60
+
61
+ # create and bind socket
62
+ @socket = CZTop::Socket::SERVER.new
63
+ @socket.CURVE_server!(broker_cert)
64
+ #socket.options.sndtimeo = 0
65
+ @socket.options.heartbeat_ivl = 100#ms
66
+ @socket.options.heartbeat_timeout = 300#ms
67
+ @socket.bind(endpoint)
68
+
69
+ puts ">>> Socket bound to #{endpoint.inspect}"
70
+
71
+ # get and print socket events
72
+ Thread.new do
73
+ monitor = CZTop::Monitor.new(@socket)
74
+ monitor.listen("ALL")
75
+ monitor.start
76
+ while msg = monitor.next
77
+ puts ">>> Socket event: #{msg.inspect}"
78
+ end
79
+ end
80
+
81
+ # receive messages from drivers
82
+ @driver_map = {} # driver name => routing ID
83
+ Thread.new do
84
+
85
+ # CZTop::Loop (zloop) doesn't work with SERVER sockets :(
86
+ poller = CZTop::Poller.new(@socket)
87
+ while true
88
+ puts "waiting for socket to become readable ..."
89
+ socket = poller.wait
90
+ puts "socket is readable"
91
+ msg = socket.receive
92
+ puts "got message"
93
+ command, argument = msg[0].split("\t", 2)
94
+
95
+ case command
96
+ when "HELLO"
97
+ driver = argument
98
+ puts ">>> Driver #{driver.inspect} has connected."
99
+ welcome = @driver_map.key?(driver) ? "WELCOMEBACK" : "WELCOME"
100
+
101
+ # remember driver's assigned message routing ID
102
+ @driver_map[driver] = msg.routing_id
103
+
104
+ # send WELCOME or WELCOMEBACK
105
+ rep = CZTop::Message.new(welcome)
106
+ rep.routing_id = @driver_map[driver]
107
+ socket << rep
108
+ puts ">>> Sent #{welcome.inspect} to #{driver.inspect}"
109
+ end
110
+ end
111
+ end
112
+
113
+ def send_command(driver, command)
114
+ if command.nil? || command.empty?
115
+ puts "!!! No message given."
116
+ return
117
+ end
118
+ if not @driver_map.key?(driver)
119
+ puts "!!! Driver #{driver.inspect} has never connected."
120
+ return
121
+ end
122
+ puts ">>> Sending message to #{driver.inspect} ..."
123
+ msg = CZTop::Message.new(command)
124
+ msg.routing_id = @driver_map[driver]
125
+ @socket << msg
126
+ rescue SocketError
127
+ puts "!!! Driver #{driver.inspect} isn't connected anymore."
128
+ end
129
+
130
+ ##
131
+ # REPL for user to play
132
+ #
133
+ puts <<MSG
134
+ You can now send messages to the drivers yourself.
135
+ The use the method #send_command, like this:
136
+
137
+ pry> send_command("driver1", "PICKUP\t(8.541694,47.376887)")
138
+
139
+ This should show something like this in the client.rb terminal:
140
+
141
+ 03:17:01 driver1.1 | received message: "PICKUP\t(8.541694,47.376887)"
142
+ MSG
143
+
144
+ binding.pry
145
+ ```
146
+
147
+ ## Client
148
+
149
+ Here you have to provide the environment variables `BROKER_ADDRESS` (ditto),
150
+ `BROKER_CERT` (public key only), `CLIENT_CERT` (taxi driver's certificate
151
+ containing the secret+public keys).
152
+
153
+ After connecting to the broker and completing the security handshake, the
154
+ client sends a `HELLO` message, after which it immediately expects some answer
155
+ from the broker (see above). After that, it just listens for messages (service
156
+ requests) and prints them into the terminal.
157
+
158
+ ```ruby
159
+ #!/usr/bin/env ruby
160
+ require_relative '../../lib/cztop'
161
+
162
+ endpoint = ENV["BROKER_ADDRESS"]
163
+ broker_cert = CZTop::Certificate.load ENV["BROKER_CERT"] # public only
164
+ client_cert = CZTop::Certificate.load ENV["CLIENT_CERT"]
165
+
166
+ @socket = CZTop::Socket::CLIENT.new
167
+ @socket.CURVE_client!(client_cert, broker_cert)
168
+ @socket.options.sndtimeo = 2000#ms
169
+
170
+ # heartbeating:
171
+ # * send PING every 100ms
172
+ # * close connection after 300ms of no life sign from broker
173
+ # * tell broker to close connection after 500ms of no life sign from client
174
+ @socket.options.heartbeat_ivl = 100#ms
175
+ @socket.options.heartbeat_timeout = 300#ms
176
+ @socket.options.heartbeat_ttl = 500#ms
177
+
178
+ @socket.connect(endpoint)
179
+ puts ">>> connected."
180
+
181
+ # tell broker who we are
182
+ @socket << "HELLO\t#{client_cert["driver_name"]}"
183
+ puts ">>> sent HELLO."
184
+ welcome = @socket.receive[0]
185
+ puts ">>> got #{welcome}."
186
+
187
+ poller = CZTop::Poller.new(@socket)
188
+ while true
189
+ socket = poller.wait
190
+ message = socket.receive
191
+ puts ">>> received message: #{message[0].inspect}"
192
+ end
193
+ ```
194
+
195
+ ## How to run the example
196
+
197
+ ### Generate broker's and drivers' keys
198
+
199
+ Here's a simple script that'll create the broker's certificate and the taxi
200
+ drivers' certificates. There are also public key only files so a minimum amount
201
+ of information can be made available on one system, e.g. a taxi driver's system
202
+ must not know the broker's secret key. Also, the broker doesn't necessarily
203
+ need to know the clients' secret keys just to authenticate them.
204
+
205
+ ```ruby
206
+ #!/usr/bin/env ruby
207
+ require_relative '../../lib/cztop'
208
+ require 'fileutils'
209
+ FileUtils.cd(File.dirname(__FILE__))
210
+ FileUtils.mkdir "public_keys"
211
+ FileUtils.mkdir "public_keys/drivers"
212
+ FileUtils.mkdir "secret_keys"
213
+ FileUtils.mkdir "secret_keys/drivers"
214
+ #FileUtils.mkdir "certs/drivers"
215
+
216
+ DRIVERS = %w[ driver1 driver2 driver3 ]
217
+
218
+ # broker certificate
219
+ cert = CZTop::Certificate.new
220
+ cert.save("secret_keys/broker")
221
+ cert.save_public("public_keys/broker")
222
+
223
+ # driver certificates
224
+ DRIVERS.each do |driver_name|
225
+ cert = CZTop::Certificate.new
226
+ cert["driver_name"] = driver_name
227
+ cert.save "secret_keys/drivers/#{driver_name}"
228
+ cert.save_public "public_keys/drivers/#{driver_name}"
229
+ end
230
+
231
+ ```
232
+ Run it as follows:
233
+
234
+ ```
235
+ ./generate_keys.rb
236
+ ```
237
+
238
+ ### Start broker
239
+
240
+ Run this:
241
+
242
+ ```
243
+ ./start_broker.sh
244
+ ```
245
+
246
+ which will execute the following script:
247
+
248
+ ```sh
249
+ #!/bin/sh -x
250
+ BROKER_ADDRESS=tcp://127.0.0.1:4455 BROKER_CERT=secret_keys/broker CLIENT_CERTS=public_keys/drivers ./broker.rb
251
+ ```
252
+
253
+ ### Start driver software instances
254
+
255
+ Run this in another terminal:
256
+
257
+ ```
258
+ ./start_clients.sh
259
+ ```
260
+
261
+ which will execute the following script:
262
+
263
+ ```sh
264
+ #!/bin/sh -x
265
+ export BROKER_ADDRESS=tcp://127.0.0.1:4455
266
+ export BROKER_CERT=public_keys/broker
267
+ CLIENT_CERT=secret_keys/drivers/driver1_secret ./client.rb &
268
+ CLIENT_CERT=secret_keys/drivers/driver2_secret ./client.rb &
269
+ CLIENT_CERT=secret_keys/drivers/driver3_secret ./client.rb &
270
+ jobs
271
+ jobs -p
272
+ jobs -l
273
+ trap 'kill $(jobs -p)' EXIT
274
+ wait
275
+ ```
276
+
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env ruby
2
+ require 'pry'
3
+ require 'pathname'
4
+ require_relative '../../lib/cztop'
5
+
6
+ endpoint = ENV["BROKER_ADDRESS"]
7
+ broker_cert = CZTop::Certificate.load ENV["BROKER_CERT"] # secret+public
8
+ client_certs = ENV["CLIENT_CERTS"] # /path/to/client_certs/
9
+ drivers = Pathname.new(client_certs).children.map(&:basename).map(&:to_s)
10
+
11
+ authenticator = CZTop::Authenticator.new
12
+ authenticator.verbose!
13
+ authenticator.curve(client_certs)
14
+
15
+ # create and bind socket
16
+ @socket = CZTop::Socket::SERVER.new
17
+ @socket.CURVE_server!(broker_cert)
18
+ #socket.options.sndtimeo = 0
19
+ @socket.options.heartbeat_ivl = 100#ms
20
+ @socket.options.heartbeat_timeout = 300#ms
21
+ @socket.bind(endpoint)
22
+
23
+ puts ">>> Socket bound to #{endpoint.inspect}"
24
+
25
+ # get and print socket events
26
+ Thread.new do
27
+ monitor = CZTop::Monitor.new(@socket)
28
+ monitor.listen("ALL")
29
+ monitor.start
30
+ while msg = monitor.next
31
+ puts ">>> Socket event: #{msg.inspect}"
32
+ end
33
+ end
34
+
35
+ # receive messages from drivers
36
+ @driver_map = {} # driver name => routing ID
37
+ Thread.new do
38
+
39
+ # CZTop::Loop (zloop) doesn't work with SERVER sockets :(
40
+ poller = CZTop::Poller.new(@socket)
41
+ while true
42
+ puts "waiting for socket to become readable ..."
43
+ socket = poller.wait
44
+ puts "socket is readable"
45
+ msg = socket.receive
46
+ puts "got message"
47
+ command, argument = msg[0].split("\t", 2)
48
+
49
+ case command
50
+ when "HELLO"
51
+ driver = argument
52
+ puts ">>> Driver #{driver.inspect} has connected."
53
+ welcome = @driver_map.key?(driver) ? "WELCOMEBACK" : "WELCOME"
54
+
55
+ # remember driver's assigned message routing ID
56
+ @driver_map[driver] = msg.routing_id
57
+
58
+ # send WELCOME or WELCOMEBACK
59
+ rep = CZTop::Message.new(welcome)
60
+ rep.routing_id = @driver_map[driver]
61
+ socket << rep
62
+ puts ">>> Sent #{welcome.inspect} to #{driver.inspect}"
63
+ end
64
+ end
65
+ end
66
+
67
+ def send_command(driver, command)
68
+ if command.nil? || command.empty?
69
+ puts "!!! No message given."
70
+ return
71
+ end
72
+ if not @driver_map.key?(driver)
73
+ puts "!!! Driver #{driver.inspect} has never connected."
74
+ return
75
+ end
76
+ puts ">>> Sending message to #{driver.inspect} ..."
77
+ msg = CZTop::Message.new(command)
78
+ msg.routing_id = @driver_map[driver]
79
+ @socket << msg
80
+ rescue SocketError
81
+ puts "!!! Driver #{driver.inspect} isn't connected anymore."
82
+ end
83
+
84
+ ##
85
+ # REPL for user to play
86
+ #
87
+ puts <<MSG
88
+ You can now send messages to the drivers yourself.
89
+ The use the method #send_command, like this:
90
+
91
+ pry> send_command("driver1", "PICKUP\t(8.541694,47.376887)")
92
+
93
+ This should show something like this in the client.rb terminal:
94
+
95
+ 03:17:01 driver1.1 | received message: "PICKUP\t(8.541694,47.376887)"
96
+ MSG
97
+
98
+ binding.pry
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../../lib/cztop'
3
+
4
+ endpoint = ENV["BROKER_ADDRESS"]
5
+ broker_cert = CZTop::Certificate.load ENV["BROKER_CERT"] # public only
6
+ client_cert = CZTop::Certificate.load ENV["CLIENT_CERT"]
7
+
8
+ @socket = CZTop::Socket::CLIENT.new
9
+ @socket.CURVE_client!(client_cert, broker_cert)
10
+ @socket.options.sndtimeo = 2000#ms
11
+
12
+ # heartbeating:
13
+ # * send PING every 100ms
14
+ # * close connection after 300ms of no life sign from broker
15
+ # * tell broker to close connection after 500ms of no life sign from client
16
+ @socket.options.heartbeat_ivl = 100#ms
17
+ @socket.options.heartbeat_timeout = 300#ms
18
+ @socket.options.heartbeat_ttl = 500#ms
19
+
20
+ @socket.connect(endpoint)
21
+ puts ">>> connected."
22
+
23
+ # tell broker who we are
24
+ @socket << "HELLO\t#{client_cert["driver_name"]}"
25
+ puts ">>> sent HELLO."
26
+ welcome = @socket.receive[0]
27
+ puts ">>> got #{welcome}."
28
+
29
+ poller = CZTop::Poller.new(@socket)
30
+ while true
31
+ socket = poller.wait
32
+ message = socket.receive
33
+ puts ">>> received message: #{message[0].inspect}"
34
+ end
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../../lib/cztop'
3
+ require 'fileutils'
4
+ FileUtils.cd(File.dirname(__FILE__))
5
+ FileUtils.mkdir "public_keys"
6
+ FileUtils.mkdir "public_keys/drivers"
7
+ FileUtils.mkdir "secret_keys"
8
+ FileUtils.mkdir "secret_keys/drivers"
9
+ #FileUtils.mkdir "certs/drivers"
10
+
11
+ DRIVERS = %w[ driver1 driver2 driver3 ]
12
+
13
+ # broker certificate
14
+ cert = CZTop::Certificate.new
15
+ cert.save("secret_keys/broker")
16
+ cert.save_public("public_keys/broker")
17
+
18
+ # driver certificates
19
+ DRIVERS.each do |driver_name|
20
+ cert = CZTop::Certificate.new
21
+ cert["driver_name"] = driver_name
22
+ cert.save "secret_keys/drivers/#{driver_name}"
23
+ cert.save_public "public_keys/drivers/#{driver_name}"
24
+ end