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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +31 -0
- data/.yardopts +1 -0
- data/AUTHORS +1 -0
- data/CHANGES.md +3 -0
- data/Gemfile +10 -0
- data/Guardfile +61 -0
- data/LICENSE +5 -0
- data/Procfile +3 -0
- data/README.md +408 -0
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/bin/setup +7 -0
- data/ci-scripts/install-deps +9 -0
- data/cztop.gemspec +36 -0
- data/examples/ruby_actor/actor.rb +100 -0
- data/examples/simple_req_rep/rep.rb +12 -0
- data/examples/simple_req_rep/req.rb +35 -0
- data/examples/taxi_system/.gitignore +2 -0
- data/examples/taxi_system/Makefile +2 -0
- data/examples/taxi_system/README.gsl +115 -0
- data/examples/taxi_system/README.md +276 -0
- data/examples/taxi_system/broker.rb +98 -0
- data/examples/taxi_system/client.rb +34 -0
- data/examples/taxi_system/generate_keys.rb +24 -0
- data/examples/taxi_system/start_broker.sh +2 -0
- data/examples/taxi_system/start_clients.sh +11 -0
- data/lib/cztop/actor.rb +308 -0
- data/lib/cztop/authenticator.rb +97 -0
- data/lib/cztop/beacon.rb +96 -0
- data/lib/cztop/certificate.rb +176 -0
- data/lib/cztop/config/comments.rb +66 -0
- data/lib/cztop/config/serialization.rb +82 -0
- data/lib/cztop/config/traversing.rb +157 -0
- data/lib/cztop/config.rb +119 -0
- data/lib/cztop/frame.rb +158 -0
- data/lib/cztop/has_ffi_delegate.rb +85 -0
- data/lib/cztop/message/frames.rb +74 -0
- data/lib/cztop/message.rb +191 -0
- data/lib/cztop/monitor.rb +102 -0
- data/lib/cztop/poller.rb +334 -0
- data/lib/cztop/polymorphic_zsock_methods.rb +24 -0
- data/lib/cztop/proxy.rb +149 -0
- data/lib/cztop/send_receive_methods.rb +35 -0
- data/lib/cztop/socket/types.rb +207 -0
- data/lib/cztop/socket.rb +106 -0
- data/lib/cztop/version.rb +3 -0
- data/lib/cztop/z85.rb +157 -0
- data/lib/cztop/zsock_options.rb +334 -0
- data/lib/cztop.rb +55 -0
- data/perf/README.md +79 -0
- data/perf/inproc_lat.rb +49 -0
- data/perf/inproc_thru.rb +42 -0
- data/perf/local_lat.rb +35 -0
- data/perf/remote_lat.rb +26 -0
- 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,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
|