easy_sockets 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 +12 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/.yardopts +6 -0
- data/CHANGELOG.rb +4 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +280 -0
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/bin/setup +6 -0
- data/easy_sockets.gemspec +33 -0
- data/examples/tcp_socket.rb +26 -0
- data/examples/unix_socket.rb +26 -0
- data/lib/easy_sockets/basic_socket.rb +154 -0
- data/lib/easy_sockets/constants.rb +8 -0
- data/lib/easy_sockets/tcp/tcp_server.rb +83 -0
- data/lib/easy_sockets/tcp/tcp_socket.rb +56 -0
- data/lib/easy_sockets/unix/unix_server.rb +83 -0
- data/lib/easy_sockets/unix/unix_socket.rb +56 -0
- data/lib/easy_sockets/utils/server_utils.rb +69 -0
- data/lib/easy_sockets/utils.rb +7 -0
- data/lib/easy_sockets/version.rb +3 -0
- data/lib/easy_sockets.rb +7 -0
- metadata +113 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6cda3ce2892a6052d57ed242708f762febe57481
|
4
|
+
data.tar.gz: fa52646d15d1aede1b25c6a612aab869c767d8da
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4160e8c1d10c54a8998c4d071de8b9f91215c157925814a36f5d097cf0804db2a1e76d58a5c6bb75117ed747d9735d2b4655873438686160a312b17684745e27
|
7
|
+
data.tar.gz: 3324c1006ccc13d583dce4d1aa6d037a94e7e8cacadc0d58aaf411ce8a73ceb88b0f642aadb25d1abaa803c9cafd5ced03b71a2d3aa1a97b41294c1ab59b70ac
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/CHANGELOG.rb
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Marcos Ortiz
|
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,280 @@
|
|
1
|
+
# Easy Sockets
|
2
|
+
|
3
|
+
[![Gem Version][GV img]][Gem Version]
|
4
|
+
[![Build Status][BS img]][Build Status]
|
5
|
+
[![Dependency Status][DS img]][Dependency Status]
|
6
|
+
[![Code Climate][CC img]][Code Climate]
|
7
|
+
[![Coverage Status][CS img]][Coverage Status]
|
8
|
+
|
9
|
+
[Gem Version]: https://rubygems.org/gems/easy_sockets
|
10
|
+
[Build Status]: https://travis-ci.org/marcosortiz/easy_sockets
|
11
|
+
[Dependency Status]: https://gemnasium.com/marcosortiz/easy_sockets
|
12
|
+
[Code Climate]: https://codeclimate.com/github/marcosortiz/easy_sockets
|
13
|
+
[Coverage Status]: https://codeclimate.com/github/marcosortiz/easy_sockets/coverage
|
14
|
+
|
15
|
+
[GV img]: https://badge.fury.io/rb/easy_sockets.svg
|
16
|
+
[BS img]: https://travis-ci.org/marcosortiz/easy_sockets.svg?branch=master
|
17
|
+
[DS img]: https://gemnasium.com/marcosortiz/easy_sockets.svg
|
18
|
+
[CC img]: https://codeclimate.com/github/marcosortiz/easy_sockets/badges/gpa.svg
|
19
|
+
[CS img]: https://codeclimate.com/github/marcosortiz/easy_sockets/badges/coverage.svg
|
20
|
+
|
21
|
+
## Description
|
22
|
+
|
23
|
+
Over and over I see developers struggling to implement basic sockets with featues available on ruby socket stdlib.
|
24
|
+
|
25
|
+
easy_sockets, takes care of basic details that usually are overlooked by developers when implementing TCP/Unix sockets from scratch.
|
26
|
+
|
27
|
+
I also strongly recommend [the following book](http://www.jstorimer.com/products/working-with-tcp-sockets) if you want to learn more about TCP sockets.
|
28
|
+
|
29
|
+
> UDP socket support is comming soon.
|
30
|
+
|
31
|
+
### Dependencies
|
32
|
+
|
33
|
+
easy_sockets only uses the following ruby stdlib gems:
|
34
|
+
|
35
|
+
- sockets
|
36
|
+
- logger
|
37
|
+
- timeout (just to raise Timeout::Error)
|
38
|
+
|
39
|
+
### Transparent idempotent connect and disconnect operations
|
40
|
+
|
41
|
+
You don't needneed to worry about connecting your socket (you can still call it if you want). All you need to do is call `send_msg`. If the socket object is not connected yet, it will automatically try to connect the socket before sending the message. You still need to disconnect your socket after using it.
|
42
|
+
|
43
|
+
The `connect` and `disconnect` methods are idempotent methods. That means you can call them over and over again and they will only try to do something (connect and disconnect respectively) when the instance of your socket object is disconnected and connected respectively.
|
44
|
+
|
45
|
+
The code bellow illustrates this:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
irb(main):001:0> require 'easy_sockets'
|
49
|
+
=> true
|
50
|
+
irb(main):002:0> s = EasySockets::TcpSocket.new
|
51
|
+
=> #<EasySockets::TcpSocket:0x007fd16bb69308 @logger=nil, @timeout=0.5, @separator="\r\n", @connected=false, @port=2000, @host="127.0.0.1">
|
52
|
+
irb(main):003:0> s.connected
|
53
|
+
=> false
|
54
|
+
irb(main):004:0> s.connect
|
55
|
+
=> true
|
56
|
+
irb(main):005:0> s.connected
|
57
|
+
=> true
|
58
|
+
irb(main):006:0> s.connect
|
59
|
+
=> nil
|
60
|
+
irb(main):007:0> s.connect
|
61
|
+
=> nil
|
62
|
+
irb(main):008:0> s.connected
|
63
|
+
=> true
|
64
|
+
irb(main):009:0> s.disconnect
|
65
|
+
=> true
|
66
|
+
irb(main):010:0> s.connected
|
67
|
+
=> false
|
68
|
+
irb(main):011:0> s.disconnect
|
69
|
+
=> nil
|
70
|
+
irb(main):012:0> s.disconnect
|
71
|
+
=> nil
|
72
|
+
```
|
73
|
+
|
74
|
+
### Safe connect, read and write timeout implementation
|
75
|
+
|
76
|
+
There is a lot of material on the internet saying [why you should not use ruby timeout stdlib](http://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/). However, over and over I see developers using the timeout stdlib for production code!
|
77
|
+
|
78
|
+
easy_sockets implements connect, read and write timeouts using [IO.select](http://ruby-doc.org/core-2.3.1/IO.html#method-c-select).
|
79
|
+
|
80
|
+
### Framing Messages
|
81
|
+
|
82
|
+
Usually, if you don't want to open and close a new connection everytime you need to send something to the server, you need to implement some sort of message framing. Openning (and closing) a new connection for each message, generates unnecessary overhead. While this might be ok for some communications where the messsage exchange rate is low, it might be a show stopper when this rate needs to be bigger.
|
83
|
+
|
84
|
+
Message framing is an agreement between client and server on the message format. That way clients and server can signal that one message is ending and another on is beginning.
|
85
|
+
|
86
|
+
There are numerous ways of framing messages. easy_sockets support 2:
|
87
|
+
|
88
|
+
1. **Message separators:** When pass the `:separator` option when creating your socket, easy_sockets will add it to the end of the message. For instance, if you setup `separator: "\r\n"` and you call `send_msg("some_message")`, the server will receive `"some_message\r\n"`.
|
89
|
+
|
90
|
+
2. **No separators**: When you pass the option `no_separator: true` when creating your socket, easy_sockets will not add anything to the end of the message. This is useful when both client and server uses a more specific protocol. For instance, both client and server know that the first 4 bytes of the message represent and little ending integer, and depending on the value of that integer, the message will have a specific size and format.
|
91
|
+
|
92
|
+
Whether to use separators or not, is totally up to what both client and server expects.
|
93
|
+
|
94
|
+
> If you decide to use new lines as the message separator, remember that it is `\n` on Unix systems but `\r\n` on Windows. So, be sure that both client and server are using the same separator.
|
95
|
+
|
96
|
+
## Installation
|
97
|
+
|
98
|
+
Add this line to your application's Gemfile:
|
99
|
+
```ruby
|
100
|
+
gem 'easy_sockets'
|
101
|
+
```
|
102
|
+
|
103
|
+
And then execute:
|
104
|
+
|
105
|
+
$ bundle
|
106
|
+
|
107
|
+
Or install it yourself as:
|
108
|
+
|
109
|
+
$ gem install easy_sockets
|
110
|
+
|
111
|
+
## Usage
|
112
|
+
|
113
|
+
Make sure you have netcat installed on your system. We will use it to emulate our servers.
|
114
|
+
|
115
|
+
### TCP Sockets
|
116
|
+
|
117
|
+
Open up a terminal window and type the following to start a TCP server:
|
118
|
+
```bash
|
119
|
+
nc -ckl 2500
|
120
|
+
```
|
121
|
+
|
122
|
+
On another terminal window, run the following [code](https://github.com/marcosortiz/easy_sockets/blob/master/examples/tcp_socket.rb) to start the client:
|
123
|
+
```ruby
|
124
|
+
require 'easy_sockets'
|
125
|
+
|
126
|
+
host = ARGV[0] || '127.0.0.1'
|
127
|
+
|
128
|
+
port = ARGV[1].to_i
|
129
|
+
port = 2500 if port <= 0
|
130
|
+
|
131
|
+
opts = {
|
132
|
+
host: host,
|
133
|
+
port: port,
|
134
|
+
timeout: 300,
|
135
|
+
separator: "\r\n",
|
136
|
+
logger: Logger.new(STDOUT),
|
137
|
+
}
|
138
|
+
s = EasySockets::TcpSocket.new(opts)
|
139
|
+
[:INT, :QUIT, :TERM].each do |signal|
|
140
|
+
Signal.trap(signal) do
|
141
|
+
exit
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
loop do
|
146
|
+
puts "Please write the message you want to send and hit ENTER, or type Ctrl+c to quit:"
|
147
|
+
msg = gets.chomp
|
148
|
+
s.send_msg(msg)
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
Then typing `sample_request` in the client terminal, you should see:
|
153
|
+
```
|
154
|
+
$ bundle exec ruby examples/tcp_socket.rb
|
155
|
+
Please write the message you want to send and hit ENTER, or type Ctrl+c to quit:
|
156
|
+
sample_request
|
157
|
+
D, [2016-06-30T15:17:39.648945 #90385] DEBUG -- : Successfully connected to tcp://127.0.0.1:2500.
|
158
|
+
D, [2016-06-30T15:17:39.649044 #90385] DEBUG -- : Sending "sample_request\r\n"
|
159
|
+
```
|
160
|
+
|
161
|
+
And the server terminal window should display:
|
162
|
+
```
|
163
|
+
$ nc -ckl 2500
|
164
|
+
sample_request
|
165
|
+
|
166
|
+
```
|
167
|
+
|
168
|
+
Then type `sample_response` on the server terminal window, and you should see:
|
169
|
+
```
|
170
|
+
$ nc -ckl 2500
|
171
|
+
sample_request
|
172
|
+
sample_response
|
173
|
+
|
174
|
+
```
|
175
|
+
|
176
|
+
And the client window should show:
|
177
|
+
```
|
178
|
+
$ bundle exec ruby examples/tcp_socket.rb
|
179
|
+
Please write the message you want to send and hit ENTER, or type Ctrl+c to quit:
|
180
|
+
sample_request
|
181
|
+
D, [2016-06-30T15:17:39.648945 #90385] DEBUG -- : Successfully connected to tcp://127.0.0.1:2500.
|
182
|
+
D, [2016-06-30T15:17:39.649044 #90385] DEBUG -- : Sending "sample_request\r\n"
|
183
|
+
D, [2016-06-30T15:19:52.494791 #90385] DEBUG -- : Got "sample_response\r\n"
|
184
|
+
Please write the message you want to send and hit ENTER, or type Ctrl+c to quit:
|
185
|
+
|
186
|
+
```
|
187
|
+
|
188
|
+
Press `Ctrl+c` on the client and server terminal windows to terminate both.
|
189
|
+
|
190
|
+
### Unix Sockets
|
191
|
+
|
192
|
+
Open up a terminal window and type the following to start a Unix server:
|
193
|
+
```bash
|
194
|
+
nc -Ul /tmp/test_socket
|
195
|
+
```
|
196
|
+
|
197
|
+
On another terminal window, run the following [code](https://github.com/marcosortiz/easy_sockets/blob/master/examples/unix_socket.rb) to start the client:
|
198
|
+
```ruby
|
199
|
+
require 'easy_sockets'
|
200
|
+
|
201
|
+
host = ARGV[0] || '127.0.0.1'
|
202
|
+
|
203
|
+
socket_path = ARGV[1]
|
204
|
+
socket_path ||= '/tmp/test_socket'
|
205
|
+
|
206
|
+
opts = {
|
207
|
+
host: host,
|
208
|
+
socket_path: socket_path,
|
209
|
+
timeout: 300,
|
210
|
+
separator: "\n",
|
211
|
+
logger: Logger.new(STDOUT),
|
212
|
+
}
|
213
|
+
s = EasySockets::UnixSocket.new(opts)
|
214
|
+
[:INT, :QUIT, :TERM].each do |signal|
|
215
|
+
Signal.trap(signal) do
|
216
|
+
exit
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
loop do
|
221
|
+
puts "Please write the message you want to send and hit ENTER, or type Ctrl+c to quit:"
|
222
|
+
msg = gets.chomp
|
223
|
+
s.send_msg(msg)
|
224
|
+
end
|
225
|
+
```
|
226
|
+
|
227
|
+
Then typing `sample_request` in the client terminal, you should see:
|
228
|
+
```
|
229
|
+
marcosortiz@~/dev/easy_sockets$ bundle exec ruby examples/unix_socket.rb
|
230
|
+
Please write the message you want to send and hit ENTER, or type Ctrl+c to quit:
|
231
|
+
sample_request
|
232
|
+
D, [2016-06-30T15:38:10.303188 #96993] DEBUG -- : Successfully connected to /tmp/test_socket.
|
233
|
+
D, [2016-06-30T15:38:10.303265 #96993] DEBUG -- : Sending "sample_request\n"
|
234
|
+
```
|
235
|
+
|
236
|
+
And the server terminal window should display:
|
237
|
+
```
|
238
|
+
$ nc -Ul /tmp/test_socket
|
239
|
+
sample_request
|
240
|
+
|
241
|
+
```
|
242
|
+
|
243
|
+
Then type `sample_response` on the server terminal window, and you should see:
|
244
|
+
```
|
245
|
+
$ nc -Ul /tmp/test_socket
|
246
|
+
sample_request
|
247
|
+
sample_response
|
248
|
+
|
249
|
+
```
|
250
|
+
|
251
|
+
And the client window should show:
|
252
|
+
```
|
253
|
+
$ bundle exec ruby examples/unix_socket.rb
|
254
|
+
Please write the message you want to send and hit ENTER, or type Ctrl+c to quit:
|
255
|
+
sample_request
|
256
|
+
D, [2016-06-30T15:38:10.303188 #96993] DEBUG -- : Successfully connected to /tmp/test_socket.
|
257
|
+
D, [2016-06-30T15:38:10.303265 #96993] DEBUG -- : Sending "sample_request\n"
|
258
|
+
D, [2016-06-30T15:38:23.503411 #96993] DEBUG -- : "sample_response\n"
|
259
|
+
D, [2016-06-30T15:38:23.503488 #96993] DEBUG -- : Got "sample_response\n"
|
260
|
+
Please write the message you want to send and hit ENTER, or type Ctrl+c to quit:
|
261
|
+
|
262
|
+
```
|
263
|
+
|
264
|
+
Press `Ctrl+c` on the client and server terminal windows to terminate both. Also, type `rm -rf /tmp/test_socket` to remove the socket file.
|
265
|
+
|
266
|
+
## Development
|
267
|
+
|
268
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
269
|
+
|
270
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
271
|
+
|
272
|
+
## 5. Contributing
|
273
|
+
|
274
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/marcosortiz/easy_sockets.
|
275
|
+
|
276
|
+
|
277
|
+
## 6. License
|
278
|
+
|
279
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
280
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'easy_sockets/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "easy_sockets"
|
8
|
+
spec.version = EasySockets::VERSION
|
9
|
+
spec.authors = ["Marcos Ortiz"]
|
10
|
+
spec.email = ["marcos.ortiz@icloud.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Wrapper around ruby socket stdlib to make developer's life easier'.}
|
13
|
+
spec.description = %q{Over and over I see developers struggling to implement basic sockets with featues available on ruby socket stdlib. easy_sockets, takes care of basic details that usually are overlooked by developers when implementing TCP/Unix sockets from scratch.}
|
14
|
+
spec.homepage = "https://github.com/marcosortiz/easy_sockets"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
# if spec.respond_to?(:metadata)
|
20
|
+
# spec.metadata['allowed_push_host'] = 'http://rubygems.org'
|
21
|
+
# else
|
22
|
+
# raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
|
+
# end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
32
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'easy_sockets'
|
2
|
+
|
3
|
+
host = ARGV[0] || '127.0.0.1'
|
4
|
+
|
5
|
+
port = ARGV[1].to_i
|
6
|
+
port = 2500 if port <= 0
|
7
|
+
|
8
|
+
opts = {
|
9
|
+
host: host,
|
10
|
+
port: port,
|
11
|
+
timeout: 300,
|
12
|
+
separator: "\r\n",
|
13
|
+
logger: Logger.new(STDOUT),
|
14
|
+
}
|
15
|
+
s = EasySockets::UnixSocket.new(opts)
|
16
|
+
[:INT, :QUIT, :TERM].each do |signal|
|
17
|
+
Signal.trap(signal) do
|
18
|
+
exit
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
loop do
|
23
|
+
puts "Please write the message you want to send and hit ENTER, or type Ctrl+c to quit:"
|
24
|
+
msg = gets.chomp
|
25
|
+
s.send_msg(msg)
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'easy_sockets'
|
2
|
+
|
3
|
+
host = ARGV[0] || '127.0.0.1'
|
4
|
+
|
5
|
+
socket_path = ARGV[1]
|
6
|
+
socket_path ||= '/tmp/test_socket'
|
7
|
+
|
8
|
+
opts = {
|
9
|
+
host: host,
|
10
|
+
socket_path: socket_path,
|
11
|
+
timeout: 300,
|
12
|
+
separator: "\n",
|
13
|
+
logger: Logger.new(STDOUT),
|
14
|
+
}
|
15
|
+
s = EasySockets::UnixSocket.new(opts)
|
16
|
+
[:INT, :QUIT, :TERM].each do |signal|
|
17
|
+
Signal.trap(signal) do
|
18
|
+
exit
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
loop do
|
23
|
+
puts "Please write the message you want to send and hit ENTER, or type Ctrl+c to quit:"
|
24
|
+
msg = gets.chomp
|
25
|
+
s.send_msg(msg)
|
26
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'socket'
|
3
|
+
require 'timeout'
|
4
|
+
require 'easy_sockets/constants'
|
5
|
+
require 'easy_sockets/utils'
|
6
|
+
|
7
|
+
module EasySockets
|
8
|
+
#
|
9
|
+
# @author Marcos Ortiz
|
10
|
+
# @abstract Please check the following subclasses: {EasySockets::TcpSocket} and {EasySockets::UnixSocket}.
|
11
|
+
#
|
12
|
+
class BasicSocket
|
13
|
+
include EasySockets::Utils
|
14
|
+
|
15
|
+
DEFAULT_TIMEOUT = 0.5
|
16
|
+
|
17
|
+
attr_reader :logger, :connected
|
18
|
+
alias_method :connected?, :connected
|
19
|
+
|
20
|
+
#
|
21
|
+
# @param [Hash] opts the options to create a socket with.
|
22
|
+
# @option opts [Logger] :logger (nil) An instance of Logger.
|
23
|
+
# @option opts [Float] :timeout (0.5) Timeout in seconds for socket connect, read and write operations.
|
24
|
+
# @option opts [String] :separator ("\r\n") Message separator.
|
25
|
+
# @option opts [Boolean] :no_msg_separator (nil) If true, the socket will not use message separators.
|
26
|
+
def initialize(opts={})
|
27
|
+
setup_opts(opts)
|
28
|
+
@connected = false
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Connects to the server. This is an idempotent operation.
|
33
|
+
#
|
34
|
+
def connect
|
35
|
+
return if @connected && (@socket && !@socket.closed?)
|
36
|
+
on_connect
|
37
|
+
@connected = true
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Disconnects to the server. This is an idempotent operation.
|
42
|
+
#
|
43
|
+
def disconnect
|
44
|
+
return unless @connected
|
45
|
+
if @socket && !@socket.closed?
|
46
|
+
@socket.close
|
47
|
+
log(:debug, "Socket successfully disconnected")
|
48
|
+
@connected = false
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Sends the message to the server, and reads and return the response if read_response=true.
|
55
|
+
# If you call this method and the socket is not connected yet, it will automatically connect the socket.
|
56
|
+
# @param [String] msg The message to send.
|
57
|
+
# @param [Boolean] read_response Whether or not to read from the server after sending the message. Defaul to true.
|
58
|
+
#
|
59
|
+
#
|
60
|
+
def send_msg(msg, read_response=true)
|
61
|
+
msg_to_send = msg.dup
|
62
|
+
msg_to_send << @separator unless @separator.nil? || msg.end_with?(@separator)
|
63
|
+
|
64
|
+
# This is an idempotent operation
|
65
|
+
connect
|
66
|
+
|
67
|
+
log(:debug, "Sending #{msg_to_send.inspect}")
|
68
|
+
send_non_block(msg_to_send)
|
69
|
+
|
70
|
+
if read_response
|
71
|
+
resp = receive_non_block
|
72
|
+
log(:debug, "Got #{resp.inspect}")
|
73
|
+
resp
|
74
|
+
end
|
75
|
+
# Raised by some IO operations when reaching the end of file. Many IO methods exist in two forms,
|
76
|
+
# one that returns nil when the end of file is reached, the other raises EOFError EOFError.
|
77
|
+
# EOFError is a subclass of IOError.
|
78
|
+
rescue EOFError => e
|
79
|
+
log(:info, "Server disconnected.")
|
80
|
+
self.disconnect
|
81
|
+
raise e
|
82
|
+
# "Connection reset by peer" is the TCP/IP equivalent of slamming the phone back on the hook.
|
83
|
+
# It's more polite than merely not replying, leaving one hanging.
|
84
|
+
# But it's not the FIN-ACK expected of the truly polite TCP/IP converseur.
|
85
|
+
rescue Errno::ECONNRESET => e
|
86
|
+
log(:info, 'Connection reset by peer.')
|
87
|
+
self.disconnect
|
88
|
+
raise e
|
89
|
+
rescue Errno::EPIPE => e
|
90
|
+
log(:info, 'Broken pipe.')
|
91
|
+
self.disconnect
|
92
|
+
raise e
|
93
|
+
rescue Errno::ECONNREFUSED => e
|
94
|
+
log(:info, 'Connection refused by peer.')
|
95
|
+
self.disconnect
|
96
|
+
raise e
|
97
|
+
rescue Exception => e
|
98
|
+
@socket.close if @socket && !@socket.closed?
|
99
|
+
raise e
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def setup_opts(opts)
|
105
|
+
@logger = opts[:logger]
|
106
|
+
@timeout = opts[:timeout].to_f || DEFAULT_TIMEOUT
|
107
|
+
@timeout = DEFAULT_TIMEOUT if @timeout <= 0
|
108
|
+
@separator = opts[:separator] || CRLF
|
109
|
+
@separator = nil if opts[:no_msg_separator] == true
|
110
|
+
end
|
111
|
+
|
112
|
+
def on_connect
|
113
|
+
end
|
114
|
+
|
115
|
+
def send_non_block(msg)
|
116
|
+
begin
|
117
|
+
loop do
|
118
|
+
bytes = @socket.write_nonblock(msg)
|
119
|
+
break if bytes >= msg.size
|
120
|
+
msg.slice!(0, bytes)
|
121
|
+
IO.select(nil, [@socket])
|
122
|
+
end
|
123
|
+
rescue Errno::EAGAIN
|
124
|
+
IO.select(nil, [@socket])
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def receive_non_block
|
129
|
+
resp = ''
|
130
|
+
begin
|
131
|
+
resp << @socket.read_nonblock(CHUNK_SIZE)
|
132
|
+
while @separator && !resp.end_with?(@separator) do
|
133
|
+
resp << @socket.read_nonblock(CHUNK_SIZE)
|
134
|
+
end
|
135
|
+
resp
|
136
|
+
rescue Errno::EAGAIN
|
137
|
+
if IO.select([@socket], nil, nil, @timeout)
|
138
|
+
retry
|
139
|
+
else
|
140
|
+
self.disconnect
|
141
|
+
raise Timeout::Error, "No response in #{@timeout} seconds."
|
142
|
+
end
|
143
|
+
rescue EOFError => e
|
144
|
+
log(:info, "Server disconnected.")
|
145
|
+
self.disconnect
|
146
|
+
raise e
|
147
|
+
rescue Errno::EPIPE => e
|
148
|
+
log(:info, "Broken pipe.")
|
149
|
+
self.disconnect
|
150
|
+
raise e
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'socket'
|
3
|
+
require 'easy_sockets/constants'
|
4
|
+
require 'easy_sockets/utils/server_utils'
|
5
|
+
|
6
|
+
module EasySockets
|
7
|
+
#
|
8
|
+
# This class was created for testing purposes only. It should not be used
|
9
|
+
# in production.
|
10
|
+
#
|
11
|
+
class TcpServer
|
12
|
+
include EasySockets::ServerUtils
|
13
|
+
|
14
|
+
attr_reader :connections
|
15
|
+
|
16
|
+
DEFAULT_TIMEOUT = 0.5 # seconds
|
17
|
+
|
18
|
+
def initialize(opts={})
|
19
|
+
set_opts(opts)
|
20
|
+
@started = false
|
21
|
+
@stop_requested = false
|
22
|
+
@connections = []
|
23
|
+
register_shutdown_signals
|
24
|
+
end
|
25
|
+
|
26
|
+
def start
|
27
|
+
return if @started
|
28
|
+
@started = true
|
29
|
+
@server = TCPServer.new(@port)
|
30
|
+
@logger.info "Listening on tcp://127.0.0.1:#{@port}"
|
31
|
+
loop do
|
32
|
+
shutdown if @stop_requested
|
33
|
+
connection = accept_non_block(@server)
|
34
|
+
@connections << connection
|
35
|
+
handle(connection)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def stop
|
40
|
+
return unless @started
|
41
|
+
@stop_requested = true
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def set_opts(opts)
|
47
|
+
@port = opts[:port].to_i
|
48
|
+
@port = DEFAULT_PORT if @port <= 0
|
49
|
+
|
50
|
+
@logger = opts[:logger] || Logger.new(STDOUT)
|
51
|
+
|
52
|
+
@separator = opts[:separator]
|
53
|
+
@separator ||= EasySockets::CRLF
|
54
|
+
|
55
|
+
@sleep_time = opts[:sleep_time].to_f
|
56
|
+
@sleep_time = 0.0001 if @sleep_time <= 0.0
|
57
|
+
|
58
|
+
@timeout = opts[:timeout].to_f
|
59
|
+
@timeout = DEFAULT_TIMEOUT if @timeout <= 0.0
|
60
|
+
end
|
61
|
+
|
62
|
+
def handle(connection)
|
63
|
+
loop do
|
64
|
+
shutdown if @stop_requested
|
65
|
+
begin
|
66
|
+
msg = read_non_block(connection)
|
67
|
+
next if msg.nil? || msg.empty?
|
68
|
+
sleep @sleep_time
|
69
|
+
write_non_block(connection, msg)
|
70
|
+
if msg.chomp == 'simulate_crash'
|
71
|
+
connection.close
|
72
|
+
break
|
73
|
+
end
|
74
|
+
rescue EOFError, Errno::ECONNRESET
|
75
|
+
connection.close
|
76
|
+
@logger.info 'Client disconnected.'
|
77
|
+
break
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'easy_sockets/basic_socket'
|
2
|
+
|
3
|
+
module EasySockets
|
4
|
+
#
|
5
|
+
# @author Marcos Ortiz
|
6
|
+
# Subclass of {EasySockets::BasicSocket} that implement a TCP socket.
|
7
|
+
#
|
8
|
+
class TcpSocket < EasySockets::BasicSocket
|
9
|
+
|
10
|
+
#
|
11
|
+
# @param [Hash] opts the options to create a socket with.
|
12
|
+
# @option opts [Integer] :port (2000) The tcp port the server is running on.
|
13
|
+
# @option opts [String] :host ('127.0.0.1') The hostname or IP address the server is running on.
|
14
|
+
#
|
15
|
+
# It also accepts all options that {EasySockets::BasicSocket#initialize} accepts
|
16
|
+
def initialize(opts={})
|
17
|
+
super(opts)
|
18
|
+
|
19
|
+
@port = opts[:port].to_i || '127.0.0.1'
|
20
|
+
@port = DEFAULT_PORT if @port <= 0
|
21
|
+
@host = opts[:host] || DEFAULT_HOST
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def on_connect
|
27
|
+
@socket = Socket.new(:INET, :STREAM)
|
28
|
+
begin
|
29
|
+
# Initiate a nonblocking connection
|
30
|
+
remote_addr = Socket.pack_sockaddr_in(@port, @host)
|
31
|
+
@socket.connect_nonblock(remote_addr)
|
32
|
+
|
33
|
+
rescue Errno::EINPROGRESS
|
34
|
+
# Indicates that the connect is in progress. We monitor the
|
35
|
+
# socket for it to become writable, signaling that the connect
|
36
|
+
# is completed.
|
37
|
+
#
|
38
|
+
# Once it retries the above block of code it
|
39
|
+
# should fall through to the EISCONN rescue block and end up
|
40
|
+
# outside this entire begin block where the socket can be used.
|
41
|
+
if IO.select(nil, [@socket], nil, @timeout)
|
42
|
+
retry
|
43
|
+
else
|
44
|
+
@socket.close if @socket && !@socket.closed?
|
45
|
+
raise Timeout::Error.new("Timeout is set to #{@timeout} seconds.")
|
46
|
+
end
|
47
|
+
rescue Errno::EISCONN
|
48
|
+
# Indicates that the connect is completed successfully.
|
49
|
+
end
|
50
|
+
log(:debug, "Successfully connected to tcp://#{@host}:#{@port}.")
|
51
|
+
rescue Exception => e
|
52
|
+
@socket.close if @socket && !@socket.closed?
|
53
|
+
raise e
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'socket'
|
3
|
+
require 'easy_sockets/constants'
|
4
|
+
require 'easy_sockets/utils/server_utils'
|
5
|
+
|
6
|
+
module EasySockets
|
7
|
+
#
|
8
|
+
# This class was created for testing purposes only. It should not be used
|
9
|
+
# in production.
|
10
|
+
#
|
11
|
+
class UnixServer
|
12
|
+
include EasySockets::ServerUtils
|
13
|
+
|
14
|
+
attr_reader :connections
|
15
|
+
|
16
|
+
DEFAULT_TIMEOUT = 0.5 # seconds
|
17
|
+
|
18
|
+
def initialize(opts={})
|
19
|
+
set_opts(opts)
|
20
|
+
@started = false
|
21
|
+
@stop_requested = false
|
22
|
+
@connections = []
|
23
|
+
register_shutdown_signals
|
24
|
+
end
|
25
|
+
|
26
|
+
def start
|
27
|
+
return if @started
|
28
|
+
@started = true
|
29
|
+
@server = UNIXServer.new(@socket_path)
|
30
|
+
@logger.info "Listening on #{@socket_path}"
|
31
|
+
loop do
|
32
|
+
shutdown if @stop_requested
|
33
|
+
connection = accept_non_block(@server)
|
34
|
+
@connections << connection
|
35
|
+
handle(connection)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def stop
|
40
|
+
return unless @started
|
41
|
+
@stop_requested = true
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def set_opts(opts)
|
47
|
+
@socket_path = opts[:socket_path]
|
48
|
+
@socket_path ||= '/tmp/unix_server'
|
49
|
+
|
50
|
+
@logger = opts[:logger] || Logger.new(STDOUT)
|
51
|
+
|
52
|
+
@separator = opts[:separator]
|
53
|
+
@separator ||= EasySockets::CRLF
|
54
|
+
|
55
|
+
@sleep_time = opts[:sleep_time].to_f
|
56
|
+
@sleep_time = 0.0001 if @sleep_time <= 0.0
|
57
|
+
|
58
|
+
@timeout = opts[:timeout].to_f
|
59
|
+
@timeout = DEFAULT_TIMEOUT if @timeout <= 0.0
|
60
|
+
end
|
61
|
+
|
62
|
+
def handle(connection)
|
63
|
+
loop do
|
64
|
+
shutdown if @stop_requested
|
65
|
+
begin
|
66
|
+
msg = read_non_block(connection)
|
67
|
+
next if msg.nil? || msg.empty?
|
68
|
+
sleep @sleep_time
|
69
|
+
write_non_block(connection, msg)
|
70
|
+
if msg.chomp == 'simulate_crash'
|
71
|
+
connection.close
|
72
|
+
break
|
73
|
+
end
|
74
|
+
rescue EOFError, Errno::ECONNRESET, Errno::EPIPE
|
75
|
+
connection.close
|
76
|
+
@logger.info 'Client disconnected.'
|
77
|
+
break
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'easy_sockets/basic_socket'
|
2
|
+
|
3
|
+
module EasySockets
|
4
|
+
#
|
5
|
+
# @author Marcos Ortiz
|
6
|
+
# Subclass of {EasySockets::BasicSocket} that implement a Unix socket.
|
7
|
+
#
|
8
|
+
class UnixSocket < EasySockets::BasicSocket
|
9
|
+
|
10
|
+
DEFAULT_SOCKET_PATH = '/tmp/unix_socket'
|
11
|
+
|
12
|
+
#
|
13
|
+
# @param [Hash] opts the options to create a socket with.
|
14
|
+
# @option opts [Integer] :socket_path ('/tmp/unix_socket') The unix socket file path.
|
15
|
+
#
|
16
|
+
# It also accepts all options that {EasySockets::BasicSocket#initialize} accepts
|
17
|
+
def initialize(opts={})
|
18
|
+
super(opts)
|
19
|
+
@socket_path = opts[:socket_path] || DEFAULT_SOCKET_PATH
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def on_connect
|
25
|
+
@socket = Socket.new(:UNIX, :STREAM)
|
26
|
+
begin
|
27
|
+
# Initiate a nonblocking connection
|
28
|
+
remote_addr = Socket.pack_sockaddr_un(@socket_path)
|
29
|
+
@socket.connect_nonblock(remote_addr)
|
30
|
+
|
31
|
+
rescue Errno::EINPROGRESS
|
32
|
+
# Indicates that the connect is in progress. We monitor the
|
33
|
+
# socket for it to become writable, signaling that the connect
|
34
|
+
# is completed.
|
35
|
+
#
|
36
|
+
# Once it retries the above block of code it
|
37
|
+
# should fall through to the EISCONN rescue block and end up
|
38
|
+
# outside this entire begin block where the socket can be used.
|
39
|
+
if IO.select(nil, [@socket], nil, @timeout)
|
40
|
+
retry
|
41
|
+
else
|
42
|
+
@socket.close if @socket && !@socket.closed?
|
43
|
+
raise Timeout::Error.new("Timeout is set to #{@timeout} seconds.")
|
44
|
+
end
|
45
|
+
rescue Errno::EISCONN
|
46
|
+
# Indicates that the connect is completed successfully.
|
47
|
+
end
|
48
|
+
|
49
|
+
log(:debug, "Successfully connected to #{@socket_path}.")
|
50
|
+
rescue Exception => e
|
51
|
+
@socket.close if @socket && !@socket.closed?
|
52
|
+
raise e
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module EasySockets
|
2
|
+
module ServerUtils
|
3
|
+
|
4
|
+
def register_shutdown_signals
|
5
|
+
[:INT, :QUIT, :TERM].each do |signal|
|
6
|
+
Signal.trap(signal) do
|
7
|
+
t = Thread.new do
|
8
|
+
stop
|
9
|
+
end
|
10
|
+
t.join
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def accept_non_block(server)
|
16
|
+
begin
|
17
|
+
connection = server.accept_nonblock
|
18
|
+
rescue Errno::EAGAIN
|
19
|
+
shutdown if @stop_requested
|
20
|
+
retry
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def read_non_block(connection)
|
25
|
+
msg = ''
|
26
|
+
begin
|
27
|
+
msg << connection.read_nonblock(EasySockets::CHUNK_SIZE)
|
28
|
+
while !msg.end_with?(@separator) do
|
29
|
+
msg << connection.read_nonblock(EasySockets::CHUNK_SIZE)
|
30
|
+
end
|
31
|
+
rescue Errno::EAGAIN
|
32
|
+
if IO.select([connection], nil, nil, @timeout)
|
33
|
+
retry
|
34
|
+
end
|
35
|
+
end
|
36
|
+
@logger.info "Got: #{msg.inspect}" unless msg.nil? || msg.empty?
|
37
|
+
msg
|
38
|
+
end
|
39
|
+
|
40
|
+
def write_non_block(connection, msg)
|
41
|
+
return 0 unless msg && msg.is_a?(String)
|
42
|
+
total_bytes = 0
|
43
|
+
begin
|
44
|
+
loop do
|
45
|
+
bytes = connection.write_nonblock(msg)
|
46
|
+
total_bytes += bytes
|
47
|
+
break if bytes >= msg.size
|
48
|
+
msg.slice!(0, bytes)
|
49
|
+
IO.select(nil, [connection])
|
50
|
+
end
|
51
|
+
@logger.info "Sent: #{msg.inspect}"
|
52
|
+
total_bytes
|
53
|
+
rescue Errno::EAGAIN
|
54
|
+
IO.select(nil, [connection], nil, @timeout)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def shutdown
|
59
|
+
if @stop_requested
|
60
|
+
@connections.each do |c|
|
61
|
+
c.close
|
62
|
+
@logger.info "Server shutting down: closed connection #{c}."
|
63
|
+
end
|
64
|
+
exit
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
data/lib/easy_sockets.rb
ADDED
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: easy_sockets
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Marcos Ortiz
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-30 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: '1.12'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: Over and over I see developers struggling to implement basic sockets
|
56
|
+
with featues available on ruby socket stdlib. easy_sockets, takes care of basic
|
57
|
+
details that usually are overlooked by developers when implementing TCP/Unix sockets
|
58
|
+
from scratch.
|
59
|
+
email:
|
60
|
+
- marcos.ortiz@icloud.com
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- ".gitignore"
|
66
|
+
- ".rspec"
|
67
|
+
- ".travis.yml"
|
68
|
+
- ".yardopts"
|
69
|
+
- CHANGELOG.rb
|
70
|
+
- Gemfile
|
71
|
+
- LICENSE.txt
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- bin/console
|
75
|
+
- bin/setup
|
76
|
+
- easy_sockets.gemspec
|
77
|
+
- examples/tcp_socket.rb
|
78
|
+
- examples/unix_socket.rb
|
79
|
+
- lib/easy_sockets.rb
|
80
|
+
- lib/easy_sockets/basic_socket.rb
|
81
|
+
- lib/easy_sockets/constants.rb
|
82
|
+
- lib/easy_sockets/tcp/tcp_server.rb
|
83
|
+
- lib/easy_sockets/tcp/tcp_socket.rb
|
84
|
+
- lib/easy_sockets/unix/unix_server.rb
|
85
|
+
- lib/easy_sockets/unix/unix_socket.rb
|
86
|
+
- lib/easy_sockets/utils.rb
|
87
|
+
- lib/easy_sockets/utils/server_utils.rb
|
88
|
+
- lib/easy_sockets/version.rb
|
89
|
+
homepage: https://github.com/marcosortiz/easy_sockets
|
90
|
+
licenses:
|
91
|
+
- MIT
|
92
|
+
metadata: {}
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 2.5.1
|
110
|
+
signing_key:
|
111
|
+
specification_version: 4
|
112
|
+
summary: Wrapper around ruby socket stdlib to make developer's life easier'.
|
113
|
+
test_files: []
|