rxio 0.10.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 +3 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +21 -0
- data/README.md +237 -0
- data/Rakefile +8 -0
- data/lib/rxio/handler_base.rb +31 -0
- data/lib/rxio/io_filters/bin_delim.rb +70 -0
- data/lib/rxio/io_filters/msg_size.rb +61 -0
- data/lib/rxio/io_filters.rb +14 -0
- data/lib/rxio/service.rb +232 -0
- data/lib/rxio/version.rb +9 -0
- data/lib/rxio.rb +13 -0
- data/rxio.gemspec +25 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e280e9488e6a0e8772dc1207a4f9ef2596a75800
|
4
|
+
data.tar.gz: a93e714f68bbf8b1a2b76d5eec9b0944489f8e2d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ab635aa9b48db9a6fd09d480982187f9ecc7b272d5a94b88c2dd89d5ae960e46c7fe00c55f48ffde01da744ca0c1147c1f615a937ac3558964b106f5ebe7e425
|
7
|
+
data.tar.gz: 06a5b6087190d7ab29e9273b7c651f2c91b09aa9e9cc51782b7c6eb4899ebd59fc68483c18f625a227e93df0fa14ff844da2016643139ed8fe38e2008490a827
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Paul Duncan
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
# RxIO
|
2
|
+
|
3
|
+
Reactive Sockets for Ruby
|
4
|
+
|
5
|
+
## Presentation
|
6
|
+
|
7
|
+
This library is an implementation of the [Reactor Pattern](https://en.wikipedia.org/wiki/Reactor_pattern) for Ruby Sockets.
|
8
|
+
This allows easy development of fast, non-blocking services.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
### Gemfile
|
13
|
+
```ruby
|
14
|
+
gem 'rxio'
|
15
|
+
```
|
16
|
+
|
17
|
+
### Terminal
|
18
|
+
```bash
|
19
|
+
gem install -V rxio
|
20
|
+
```
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
A simple service is created by spawning an instance of the *RxIO::Service* class, passing a *Handler Module* as argument to its constructor and calling it's _run_ method.
|
25
|
+
|
26
|
+
This will execute the service's main loop. The handler module provides the specific protocol implementation through a set of methods.
|
27
|
+
|
28
|
+
The _run_ method is blocking and will only return after the service has terminated.
|
29
|
+
|
30
|
+
A _stop_ method is also provided to request the service to terminate.
|
31
|
+
|
32
|
+
### Running in the background
|
33
|
+
|
34
|
+
While the _run_ / _stop_ methods offer a simple way to implement a single service within the current thread, some situations may require the wrapping of the service in a separate thread.
|
35
|
+
|
36
|
+
A dedicated interface is provided for exactly this: _startup_ / _shutdown_
|
37
|
+
|
38
|
+
#### *startup*
|
39
|
+
|
40
|
+
Calling this method will spawn a new thread around the _run_ method, effectively starting the service in the 'background'.
|
41
|
+
|
42
|
+
#### *shutdown*
|
43
|
+
|
44
|
+
Calling _shutdown_ will request the service to terminate (via the _stop_ method) and wait for the thread to complete (join).
|
45
|
+
Once this method returns, the service has completely terminated (thread dead, all clients disconnected).
|
46
|
+
|
47
|
+
### Handler Interface
|
48
|
+
|
49
|
+
The following is a list of methods that should be implemented by the handler module in order to provide a valid interface to the RxIO::Service class that will use it.
|
50
|
+
|
51
|
+
#### *filter_input* _client_, _chunk_
|
52
|
+
|
53
|
+
This method is called any time a chunk of data is received from a client.
|
54
|
+
Its purpose is usually to filter protocol data and re-assemble messages.
|
55
|
+
Complete messages can be enqueued for processing by pushing them into the *client[:msgs]* array.
|
56
|
+
|
57
|
+
#### *handle_msg* _client_, _msg_
|
58
|
+
|
59
|
+
Messages in the *client[:msgs]* array are regularly de-queued and passed to the *handle_msg* method for handling.
|
60
|
+
This is usually the entry point to most of the service logic.
|
61
|
+
|
62
|
+
#### (OPTIONAL) *on_join* _client_
|
63
|
+
|
64
|
+
As soon as a client is registered by the service, it is passed as argument to the _on_join_ method of the handler module (if available).
|
65
|
+
This allows the handler module to perform any necessary initialization tasks related to this client.
|
66
|
+
|
67
|
+
#### (OPTIONAL) *on_drop* _client_
|
68
|
+
|
69
|
+
Called by the service whenever a client is about to be disconnected (if available).
|
70
|
+
This method should release any resources used by the client.
|
71
|
+
|
72
|
+
#### (OPTIONAL) *send_msg* _client_, _msg_
|
73
|
+
|
74
|
+
This method should wrap the message supplied as argument according to the desired protocol, and then add the result to the output buffer.
|
75
|
+
While not actually required by the service class, this method should be considered a best-practice for users to encapsulate data according to a protocol.
|
76
|
+
|
77
|
+
### Handler Base
|
78
|
+
|
79
|
+
A base module is provided to facilitate implementation of new services and protocols.
|
80
|
+
The *RxIO::HandlerBase* module should be extended by the service handler module.
|
81
|
+
It provides two simple I/O methods:
|
82
|
+
|
83
|
+
#### *write* _client_, _*data_
|
84
|
+
|
85
|
+
Used to send one or more data chunks to a client.
|
86
|
+
This method pushes the chunk(s) to the output buffer, to be later sent to the client.
|
87
|
+
|
88
|
+
This method is *thread-safe* - while in most cases it will be called from within the main service loop (generally as a consequence of service logic in _handle_msg_), it is perfectly safe to call from any other thread. This can be useful in situations where a service might generate messages for the client _outside_ of the normal request-response cycle (such as an event-subscription service).
|
89
|
+
|
90
|
+
Note: this method does not perform any encoding or transformation on the data. It is the user's responsibility to provide protocol encoding, usually through the _send_msg_ method.
|
91
|
+
|
92
|
+
#### *buffer_input*, _client_, _chunk_
|
93
|
+
|
94
|
+
Used to add a data chunk to the client's input buffer.
|
95
|
+
|
96
|
+
### Provided Filters
|
97
|
+
|
98
|
+
Some generic filters are provided as part of the *RxIO::IOFilters* module.
|
99
|
+
These can be extended directly in a custom service handler module to provide immediate handling of a variety of protocol types.
|
100
|
+
Most of the provided filters usually provide the _filter_input_ and _send_msg_ methods.
|
101
|
+
|
102
|
+
#### Binary-Delimiter (BinDelim)
|
103
|
+
|
104
|
+
The binary-delimiter filter is the simplest of all filters, splitting messages according to a fixed binary string.
|
105
|
+
After extending the *RxIO::IOFilters::BinDelim* module, the _msg_delim_ method should be called with the desired binary string as argument.
|
106
|
+
|
107
|
+
##### Example
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
require 'rxio'
|
111
|
+
|
112
|
+
# Custom Service 'Foo'
|
113
|
+
class FooService < RxIO::Service
|
114
|
+
|
115
|
+
# Construct
|
116
|
+
def initialize addr, port
|
117
|
+
super addr, port, FooHandler
|
118
|
+
end
|
119
|
+
|
120
|
+
# Foo Service Handler Module
|
121
|
+
module FooHandler
|
122
|
+
|
123
|
+
# Extend BinDelim I/O Filter
|
124
|
+
extend RxIO::IOFilters::BinDelim
|
125
|
+
|
126
|
+
# Set Message Delimiter
|
127
|
+
msg_delim "\n"
|
128
|
+
|
129
|
+
# ...
|
130
|
+
end
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
#### Message-Size (MsgSize)
|
135
|
+
|
136
|
+
the message-size filter is another very basic generic protocol filter, splitting messages according to a _4-byte unsigned big-endian integer_ *size* field, which prefixes every message and indicates its length in bytes.
|
137
|
+
|
138
|
+
##### Example
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
require 'rxio'
|
142
|
+
|
143
|
+
# Custom Service 'Foo'
|
144
|
+
class FooService < RxIO::Service
|
145
|
+
|
146
|
+
# Construct
|
147
|
+
def initialize addr, port
|
148
|
+
super addr, port, FooHandler
|
149
|
+
end
|
150
|
+
|
151
|
+
# Foo Service Handler Module
|
152
|
+
module FooHandler
|
153
|
+
|
154
|
+
# Extend MsgSize I/O Filter
|
155
|
+
extend RxIO::IOFilters::MsgSize
|
156
|
+
|
157
|
+
# ...
|
158
|
+
end
|
159
|
+
end
|
160
|
+
```
|
161
|
+
|
162
|
+
## Examples
|
163
|
+
|
164
|
+
### A simple echo service
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
#!/usr/bin/env ruby
|
168
|
+
|
169
|
+
require 'rxio'
|
170
|
+
|
171
|
+
# Echo Service Class
|
172
|
+
class EchoService < RxIO::Service
|
173
|
+
|
174
|
+
# Defaults
|
175
|
+
DEFAULT_ADDR = '0.0.0.0'
|
176
|
+
DEFAULT_PORT = 4444
|
177
|
+
|
178
|
+
# Construct
|
179
|
+
def initialize
|
180
|
+
super DEFAULT_ADDR, DEFAULT_PORT, EchoHandler
|
181
|
+
end
|
182
|
+
|
183
|
+
# Echo Service Handler Module
|
184
|
+
module EchoHandler
|
185
|
+
|
186
|
+
# Extend BinDelim I/O Filter
|
187
|
+
extend RxIO::IOFilters::BinDelim
|
188
|
+
|
189
|
+
# Set Message Delimiter
|
190
|
+
msg_delim "\n"
|
191
|
+
|
192
|
+
# On Join
|
193
|
+
def self.on_join client
|
194
|
+
puts "Client connected: #{client[:peer][:name]}:#{client[:peer][:port]}"
|
195
|
+
end
|
196
|
+
|
197
|
+
# On Drop
|
198
|
+
def self.on_drop client
|
199
|
+
puts "Client dropped: #{client[:peer][:name]}:#{client[:peer][:port]}"
|
200
|
+
end
|
201
|
+
|
202
|
+
# Handle Message
|
203
|
+
def self.handle_msg client, msg
|
204
|
+
msg.chomp! # Clean spurious \r
|
205
|
+
puts "Got message from client: #{client[:peer][:name]}:#{client[:peer][:port]} -> \"#{msg}\""
|
206
|
+
send_msg client, msg # Echo message back to client
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# Run the service
|
212
|
+
es = EchoService.new
|
213
|
+
es.run
|
214
|
+
```
|
215
|
+
|
216
|
+
### Running a service in the background
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
# ...
|
220
|
+
|
221
|
+
# Run the service in the background
|
222
|
+
puts 'Service is starting up...'
|
223
|
+
es = EchoService.new
|
224
|
+
es.startup
|
225
|
+
puts 'Service is online!'
|
226
|
+
|
227
|
+
# Do something while the service is running...
|
228
|
+
|
229
|
+
# Shutdown the service
|
230
|
+
es.shutdown
|
231
|
+
puts 'Service has terminated!'
|
232
|
+
|
233
|
+
```
|
234
|
+
|
235
|
+
## License
|
236
|
+
|
237
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# RxIO
|
2
|
+
# by Eresse <eresse@eresse.net>
|
3
|
+
|
4
|
+
# RxIO Module
|
5
|
+
module RxIO
|
6
|
+
|
7
|
+
# Handler Base Module
|
8
|
+
# Provides common abstractions to Service Handler implementations.
|
9
|
+
module HandlerBase
|
10
|
+
|
11
|
+
# Write
|
12
|
+
# Writes one or more chunks of data to the client's output buffer (:obuf).
|
13
|
+
# @param [Hash] client
|
14
|
+
# @param [String] data One or more chunks of data to be written to the ouput buffer
|
15
|
+
def write client, *data
|
16
|
+
|
17
|
+
# Add Data Chunks to Buffer
|
18
|
+
data.each { |c| client[:lock].synchronize { client[:obuf] << c } }
|
19
|
+
end
|
20
|
+
|
21
|
+
# Buffer Input Chunk
|
22
|
+
# Writes a chunk of data to the client's input buffer (:ibuf).
|
23
|
+
# @param [Hash] client
|
24
|
+
# @param [String] chunk
|
25
|
+
def buffer_input client, chunk
|
26
|
+
|
27
|
+
# Buffer Chunk
|
28
|
+
client[:ibuf] << chunk
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# RxIO
|
2
|
+
# by Eresse <eresse@eresse.net>
|
3
|
+
|
4
|
+
# Internal Includes
|
5
|
+
require 'rxio/handler_base'
|
6
|
+
|
7
|
+
# RxIO Module
|
8
|
+
module RxIO
|
9
|
+
|
10
|
+
# I/O Filters Module
|
11
|
+
module IOFilters
|
12
|
+
|
13
|
+
# Binary-Delimiter I/O Filter
|
14
|
+
# Splits messages according to a given fixed *binary delimiter*, which can be any number of bytes, defined through the _msg_delim_ method.
|
15
|
+
module BinDelim
|
16
|
+
|
17
|
+
# Inject Dependencies into Extending Module
|
18
|
+
# @param [Module] base
|
19
|
+
def self.extended base
|
20
|
+
base.extend RxIO::HandlerBase
|
21
|
+
end
|
22
|
+
|
23
|
+
# Set Message Delimiter
|
24
|
+
# Used to define the binary string used as message delimiter for this protocol.
|
25
|
+
# @param [String] v The message delimiter string
|
26
|
+
def msg_delim v
|
27
|
+
@msg_delim = v
|
28
|
+
end
|
29
|
+
|
30
|
+
# Filter Input
|
31
|
+
# Buffers data chunks sent by the client and extracts messages from them, according to the delimiter defined through _msg_delim_.
|
32
|
+
# @param [Hash] client
|
33
|
+
# @param [String] chunk
|
34
|
+
def filter_input client, chunk
|
35
|
+
|
36
|
+
# Buffer dat shit
|
37
|
+
buffer_input client, chunk
|
38
|
+
|
39
|
+
# Ensure Last Position is available
|
40
|
+
client[:bin_delim][:last_pos] ||= 0
|
41
|
+
|
42
|
+
# Loop over Messages
|
43
|
+
while true
|
44
|
+
|
45
|
+
# Find Delimiter
|
46
|
+
d = client[:ibuf].index @msg_delim, client[:bin_delim][:last_pos]
|
47
|
+
client[:bin_delim][:last_pos] = client[:ibuf].bytesize
|
48
|
+
|
49
|
+
# Check Delimiter
|
50
|
+
break unless d
|
51
|
+
|
52
|
+
# Slice out Message from Input Buffer
|
53
|
+
m = client[:ibuf].slice!(0, d + @msg_delim.bytesize).slice 0, d
|
54
|
+
client[:bin_delim][:last_pos] = 0
|
55
|
+
|
56
|
+
# Register Message
|
57
|
+
client[:msgs] << m
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Send Message
|
62
|
+
# Buffers a message to be sent to the client, after wrapping it according to the delimiter defined through _msg_delim_.
|
63
|
+
# @param [Hash] client
|
64
|
+
# @param [String] msg
|
65
|
+
def send_msg client, msg
|
66
|
+
write client, msg, @msg_delim
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# RxIO
|
2
|
+
# by Eresse <eresse@eresse.net>
|
3
|
+
|
4
|
+
# Internal Includes
|
5
|
+
require 'rxio/handler_base'
|
6
|
+
|
7
|
+
# RxIO Module
|
8
|
+
module RxIO
|
9
|
+
|
10
|
+
# I/O Filters Module
|
11
|
+
module IOFilters
|
12
|
+
|
13
|
+
# Message-Size I/O Filter
|
14
|
+
# Splits messages according to a _4-byte unsigned big-endian integer_ *size* field, which prefixes every message and indicates its length in bytes.
|
15
|
+
module MsgSize
|
16
|
+
|
17
|
+
# Inject Dependencies into Extending Module
|
18
|
+
# @param [Module] base
|
19
|
+
def self.extended base
|
20
|
+
base.extend RxIO::HandlerBase
|
21
|
+
end
|
22
|
+
|
23
|
+
# Filter Input
|
24
|
+
# Buffers data chunks sent by the client and extracts messages from them, according to the *size* field present at the beginning of each message.
|
25
|
+
# @param [Hash] client
|
26
|
+
# @param [String] chunk
|
27
|
+
def filter_input client, chunk
|
28
|
+
|
29
|
+
# Buffer dat shit
|
30
|
+
buffer_input client, chunk
|
31
|
+
|
32
|
+
# Loop over Messages
|
33
|
+
while true
|
34
|
+
|
35
|
+
# Check Buffer Size (can we at least determine the next message size?)
|
36
|
+
break unless client[:ibuf].bytesize >= 4
|
37
|
+
|
38
|
+
# Acquire Message Size
|
39
|
+
size = client[:ibuf][0, 4].unpack 'N'
|
40
|
+
|
41
|
+
# Check Buffer Size again (is the complete message present in the buffer?)
|
42
|
+
break unless client[:ibuf].bytesize >= 4 + size
|
43
|
+
|
44
|
+
# Slice out Message from Input Buffer
|
45
|
+
m = client[:ibuf].slice!(0, 4 + size)[4, size]
|
46
|
+
|
47
|
+
# Register Message
|
48
|
+
client[:msgs] << m
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Send Message
|
53
|
+
# Buffers a message to be sent to the client, after prefixing it with a _size_ field.
|
54
|
+
# @param [Hash] client
|
55
|
+
# @param [String] msg
|
56
|
+
def send_msg client, msg
|
57
|
+
write client, [msg.bytesize].pack('N'), msg
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# RxIO
|
2
|
+
# by Eresse <eresse@eresse.net>
|
3
|
+
|
4
|
+
# Internal Includes
|
5
|
+
require 'rxio/io_filters/bin_delim'
|
6
|
+
|
7
|
+
# RxIO Module
|
8
|
+
module RxIO
|
9
|
+
|
10
|
+
# I/O Filters Module
|
11
|
+
# This module provides a set of generic I/O Filters to be used for constructing services.
|
12
|
+
module IOFilters
|
13
|
+
end
|
14
|
+
end
|
data/lib/rxio/service.rb
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
# RxIO
|
2
|
+
# by Eresse <eresse@eresse.net>
|
3
|
+
|
4
|
+
# External Includes
|
5
|
+
require 'thread'
|
6
|
+
require 'socket'
|
7
|
+
|
8
|
+
# RxIO Module
|
9
|
+
module RxIO
|
10
|
+
|
11
|
+
# Service Class
|
12
|
+
class Service
|
13
|
+
|
14
|
+
# Chunk Size
|
15
|
+
CHUNK_SIZE = 1024
|
16
|
+
|
17
|
+
# Construct
|
18
|
+
# Builds a *Service* around a given _service_handler_ module, set to listen for incoming connections @ _addr_ on _port_.
|
19
|
+
# @param [String] addr
|
20
|
+
# @param [Fixnum] port
|
21
|
+
# @param [Module] service_handler
|
22
|
+
def initialize addr, port, service_handler
|
23
|
+
|
24
|
+
# Set Address & Port
|
25
|
+
@addr = addr
|
26
|
+
@port = port
|
27
|
+
|
28
|
+
# Set Service Handler Module
|
29
|
+
@service_handler = service_handler
|
30
|
+
|
31
|
+
# Create Sockets
|
32
|
+
@socks = []
|
33
|
+
|
34
|
+
# Create Clients
|
35
|
+
@clients = []
|
36
|
+
|
37
|
+
# Create Client Map
|
38
|
+
@cmap = {}
|
39
|
+
end
|
40
|
+
|
41
|
+
# Start up
|
42
|
+
# Spawns a new Thread around the _run_ method to execute the service in the background.
|
43
|
+
def startup
|
44
|
+
@thread = Thread.new { run }
|
45
|
+
end
|
46
|
+
|
47
|
+
# Shutdown
|
48
|
+
# Gracefully terminates the service, waiting for all resources to be released (returns once the service thread has completed).
|
49
|
+
def shutdown
|
50
|
+
@stop = true
|
51
|
+
@thread.join
|
52
|
+
@thread = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
# Run
|
56
|
+
# Executes the main service loop, taking care of I/O scheduling, client management and message handling.
|
57
|
+
# Note: this method blocks until the service loop terminates.
|
58
|
+
def run
|
59
|
+
|
60
|
+
# Update Loop
|
61
|
+
begin
|
62
|
+
|
63
|
+
# Create TCP Socket Server
|
64
|
+
@serv = TCPServer.new @addr, @port
|
65
|
+
@socks << @serv
|
66
|
+
|
67
|
+
# Update Service
|
68
|
+
update until @stop
|
69
|
+
rescue Exception => e
|
70
|
+
puts "[!] ERROR - RxIO Service Update failed - #{e.inspect}"
|
71
|
+
e.backtrace.each { |b| puts " - #{b}" }
|
72
|
+
end
|
73
|
+
|
74
|
+
# Drop all Clients
|
75
|
+
@service_handler.on_drop @clients.pop until @clients.empty?
|
76
|
+
@cmap = {}
|
77
|
+
|
78
|
+
# Close all Sockets
|
79
|
+
@socks.each { |s| s.close }
|
80
|
+
@socks = []
|
81
|
+
|
82
|
+
# Drop Server
|
83
|
+
@serv = nil
|
84
|
+
end
|
85
|
+
|
86
|
+
# Stop
|
87
|
+
# Requests the service loop to stop executing.
|
88
|
+
# The _run_ method should return shortly after calling _stop_
|
89
|
+
def stop
|
90
|
+
@stop = true
|
91
|
+
end
|
92
|
+
|
93
|
+
# Privates
|
94
|
+
private
|
95
|
+
|
96
|
+
# Update
|
97
|
+
# Serves as the service loop main method, performing I/O scheduling, client management and message handling.
|
98
|
+
def update
|
99
|
+
|
100
|
+
# Collect Sockets with Output
|
101
|
+
ws = @clients.reject { |c| c[:lock].synchronize { c[:obuf].empty? } }.collect { |c| c[:sock] }
|
102
|
+
|
103
|
+
# Select Sockets
|
104
|
+
rd, wr = IO.select @socks, ws
|
105
|
+
|
106
|
+
# Handle I/O
|
107
|
+
rd.each { |s| s == @serv ? acpt_sock(s) : read_sock(s) }
|
108
|
+
wr.each { |s| write_sock s }
|
109
|
+
end
|
110
|
+
|
111
|
+
# Process Input
|
112
|
+
# Processes Input from a client, in the form of a data chunk
|
113
|
+
# @param [Hash] client
|
114
|
+
# @param [String] chunk A chunk of data, as received by the socket
|
115
|
+
def process_input client, chunk
|
116
|
+
|
117
|
+
# Pass through Service Handler Module
|
118
|
+
@service_handler.filter_input client, chunk
|
119
|
+
|
120
|
+
# Process Messages
|
121
|
+
@service_handler.handle_msg client, client[:msgs].shift until client[:msgs].empty?
|
122
|
+
end
|
123
|
+
|
124
|
+
# Register Client
|
125
|
+
# Creates a new Client around a given socket _s_ and registers it.
|
126
|
+
# Also, notifies the Handler Module if the _on_join_ method is available.
|
127
|
+
# @param [TCPSocket] s
|
128
|
+
def add_client s
|
129
|
+
|
130
|
+
# Register Socket
|
131
|
+
@socks << s
|
132
|
+
|
133
|
+
# Acquire Peer Address
|
134
|
+
peer = s.peeraddr
|
135
|
+
|
136
|
+
# Create Client
|
137
|
+
c = {
|
138
|
+
sock: s,
|
139
|
+
peer: {
|
140
|
+
port: peer[1],
|
141
|
+
name: peer[2],
|
142
|
+
addr: peer[3]
|
143
|
+
},
|
144
|
+
ibuf: '',
|
145
|
+
obuf: '',
|
146
|
+
lock: Mutex.new,
|
147
|
+
msgs: []
|
148
|
+
}
|
149
|
+
|
150
|
+
# Register Client
|
151
|
+
@clients << c
|
152
|
+
|
153
|
+
# Map Client
|
154
|
+
@cmap[s] = c
|
155
|
+
|
156
|
+
# Notify Service Handler
|
157
|
+
@service_handler.on_join c if @service_handler.respond_to? :on_join
|
158
|
+
end
|
159
|
+
|
160
|
+
# Drop Client
|
161
|
+
# Unregisters a Client and closes the associated socket.
|
162
|
+
# Also, notifies the Handler Module if the _on_drop_ method is available.
|
163
|
+
# @param [Hash] c
|
164
|
+
def drop_client c
|
165
|
+
|
166
|
+
# Notify Service Handler
|
167
|
+
@service_handler.on_drop c if @service_handler.respond_to? :on_drop
|
168
|
+
|
169
|
+
# Drop Client
|
170
|
+
@cmap.delete c[:sock]
|
171
|
+
@socks.delete c[:sock]
|
172
|
+
@clients.delete c
|
173
|
+
|
174
|
+
# Kill Socket
|
175
|
+
c[:sock].close rescue nil
|
176
|
+
end
|
177
|
+
|
178
|
+
# Accept Socket
|
179
|
+
# Tries to accept any queued connection request in a non-blocking manner.
|
180
|
+
# Registers a new Client through _add_client_ around the newly-accepted socket if present.
|
181
|
+
# @param [TCPServer] s
|
182
|
+
def acpt_sock s
|
183
|
+
|
184
|
+
# Accept
|
185
|
+
ns = s.accept_nonblock
|
186
|
+
|
187
|
+
# Register Client
|
188
|
+
add_client ns if ns
|
189
|
+
end
|
190
|
+
|
191
|
+
# Read Socket
|
192
|
+
# Attempts to read as many bytes as possible (up to CHUNK_SIZE) from a given socket _s_, passing the data chunks to _process_input_.
|
193
|
+
# @param [TCPSocket] s
|
194
|
+
def read_sock s
|
195
|
+
|
196
|
+
# Acquire Client
|
197
|
+
c = @cmap[s]
|
198
|
+
|
199
|
+
# Check Client
|
200
|
+
return unless c
|
201
|
+
|
202
|
+
# Read Chunk from Socket
|
203
|
+
chunk = s.read_nonblock CHUNK_SIZE rescue nil
|
204
|
+
|
205
|
+
# Drop Client & Abort on Error
|
206
|
+
return drop_client c unless chunk
|
207
|
+
|
208
|
+
# Process Input
|
209
|
+
process_input c, chunk
|
210
|
+
end
|
211
|
+
|
212
|
+
# Write Socket
|
213
|
+
# Attempts to write as many bytes as possible to a given socket _s_ from the associated client's output buffer.
|
214
|
+
# @param [TCPSocket] s
|
215
|
+
def write_sock s
|
216
|
+
|
217
|
+
# Acquire Client
|
218
|
+
c = @cmap[s]
|
219
|
+
|
220
|
+
# Check Client
|
221
|
+
return unless c
|
222
|
+
|
223
|
+
# Synchronize Client
|
224
|
+
c[:lock].synchronize do
|
225
|
+
|
226
|
+
# Write as much as possible
|
227
|
+
size = s.write_nonblock c[:obuf]
|
228
|
+
c[:obuf].slice! 0, size if size > 0
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
data/lib/rxio/version.rb
ADDED
data/lib/rxio.rb
ADDED
data/rxio.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rxio/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'rxio'
|
8
|
+
spec.version = RxIO::VERSION
|
9
|
+
spec.authors = ['Eresse']
|
10
|
+
spec.email = ['eresse@eresse.net']
|
11
|
+
|
12
|
+
spec.summary = 'Reactive Sockets for Ruby'
|
13
|
+
spec.description = 'Provides an implementation of the Reactor Pattern for Ruby Sockets.'
|
14
|
+
spec.homepage = 'http://redmine.eresse.net/projects/rxio'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = 'exe'
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler'
|
23
|
+
spec.add_development_dependency 'rake'
|
24
|
+
spec.add_runtime_dependency 'minitest'
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rxio
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.10.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eresse
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Provides an implementation of the Reactor Pattern for Ruby Sockets.
|
56
|
+
email:
|
57
|
+
- eresse@eresse.net
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- lib/rxio.rb
|
68
|
+
- lib/rxio/handler_base.rb
|
69
|
+
- lib/rxio/io_filters.rb
|
70
|
+
- lib/rxio/io_filters/bin_delim.rb
|
71
|
+
- lib/rxio/io_filters/msg_size.rb
|
72
|
+
- lib/rxio/service.rb
|
73
|
+
- lib/rxio/version.rb
|
74
|
+
- rxio.gemspec
|
75
|
+
homepage: http://redmine.eresse.net/projects/rxio
|
76
|
+
licenses:
|
77
|
+
- MIT
|
78
|
+
metadata: {}
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 2.6.10
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: Reactive Sockets for Ruby
|
99
|
+
test_files: []
|