cztop 0.1.0

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