async-io 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +18 -0
- data/Gemfile +20 -0
- data/README.md +111 -0
- data/Rakefile +6 -0
- data/async-io.gemspec +25 -0
- data/lib/async/io.rb +34 -0
- data/lib/async/io/address.rb +144 -0
- data/lib/async/io/generic.rb +127 -0
- data/lib/async/io/socket.rb +159 -0
- data/lib/async/io/tcp_socket.rb +37 -0
- data/lib/async/io/udp_socket.rb +36 -0
- data/lib/async/io/unix_socket.rb +52 -0
- data/lib/async/io/version.rb +25 -0
- data/lib/async/io/wrap/tcp.rb +62 -0
- data/spec/async/io/address_spec.rb +45 -0
- data/spec/async/io/echo_spec.rb +72 -0
- data/spec/async/io/generic_spec.rb +47 -0
- data/spec/async/io/tcp_socket_spec.rb +115 -0
- data/spec/async/io/udp_socket_spec.rb +73 -0
- data/spec/async/io/unix_socket_spec.rb +55 -0
- data/spec/async/io/wrap/tcp_spec.rb +40 -0
- data/spec/spec_helper.rb +32 -0
- metadata +145 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'socket'
|
22
|
+
require_relative 'generic'
|
23
|
+
|
24
|
+
module Async
|
25
|
+
module IO
|
26
|
+
class BasicSocket < Generic
|
27
|
+
wraps ::BasicSocket, :setsockopt, :connect_address, :local_address, :remote_address, :do_not_reverse_lookup, :do_not_reverse_lookup=, :shutdown, :getsockopt, :getsockname, :getpeername, :getpeereid
|
28
|
+
|
29
|
+
wrap_blocking_method :recv, :recv_nonblock
|
30
|
+
wrap_blocking_method :recvmsg, :recvmsg_nonblock
|
31
|
+
|
32
|
+
wrap_blocking_method :recvfrom, :recvfrom_nonblock
|
33
|
+
|
34
|
+
wrap_blocking_method :sendmsg, :sendmsg_nonblock
|
35
|
+
wrap_blocking_method :send, :sendmsg_nonblock, invert: false
|
36
|
+
end
|
37
|
+
|
38
|
+
module ServerSocket
|
39
|
+
def accept
|
40
|
+
peer, address = async_send(:accept_nonblock)
|
41
|
+
|
42
|
+
if block_given?
|
43
|
+
wrapper = Socket.new(peer, self.reactor)
|
44
|
+
|
45
|
+
begin
|
46
|
+
yield wrapper, address
|
47
|
+
ensure
|
48
|
+
wrapper.close
|
49
|
+
end
|
50
|
+
else
|
51
|
+
return Socket.new(peer, self.reactor), address
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def accept_each(task: Task.current)
|
56
|
+
task.annotate "accepting connections #{self.local_address.inspect}"
|
57
|
+
|
58
|
+
while true
|
59
|
+
task.async(*self.accept) do |task, io, address|
|
60
|
+
task.annotate "incoming connection #{address}"
|
61
|
+
|
62
|
+
begin
|
63
|
+
yield io, address
|
64
|
+
ensure
|
65
|
+
io.close
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Socket < BasicSocket
|
73
|
+
wraps ::Socket, :bind, :ipv6only!, :listen
|
74
|
+
|
75
|
+
include ::Socket::Constants
|
76
|
+
|
77
|
+
include ServerSocket
|
78
|
+
|
79
|
+
def connect(*args)
|
80
|
+
begin
|
81
|
+
async_send(:connect_nonblock, *args)
|
82
|
+
rescue Errno::EISCONN
|
83
|
+
# We are now connected.
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Establish a connection to a given `remote_address`.
|
88
|
+
# @example
|
89
|
+
# socket = Async::IO::Socket.connect(Async::IO::Address.tcp("8.8.8.8", 53))
|
90
|
+
# @param remote_address [Addrinfo] The remote address to connect to.
|
91
|
+
# @param local_address [Addrinfo] The local address to bind to before connecting.
|
92
|
+
# @option protcol [Integer] The socket protocol to use.
|
93
|
+
def self.connect(remote_address, local_address = nil, protocol: 0, task: Task.current)
|
94
|
+
task.annotate "connecting to #{remote_address.inspect}"
|
95
|
+
|
96
|
+
socket = ::Socket.new(remote_address.afamily, remote_address.socktype, protocol)
|
97
|
+
|
98
|
+
if local_address
|
99
|
+
socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true)
|
100
|
+
socket.bind(local_address.to_sockaddr) if local_address
|
101
|
+
end
|
102
|
+
|
103
|
+
wrapper = self.new(socket, task.reactor)
|
104
|
+
wrapper.connect(remote_address.to_sockaddr)
|
105
|
+
|
106
|
+
task.annotate "connected to #{remote_address.inspect}"
|
107
|
+
|
108
|
+
if block_given?
|
109
|
+
begin
|
110
|
+
return yield(wrapper)
|
111
|
+
ensure
|
112
|
+
wrapper.close
|
113
|
+
end
|
114
|
+
else
|
115
|
+
return wrapper
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Bind to a local address.
|
120
|
+
# @example
|
121
|
+
# socket = Async::IO::Socket.bind(Async::IO::Address.tcp("0.0.0.0", 9090))
|
122
|
+
# @param local_address [Address] The local address to bind to.
|
123
|
+
# @option protcol [Integer] The socket protocol to use.
|
124
|
+
def self.bind(local_address, protocol: 0, task: Task.current, &block)
|
125
|
+
task.annotate "binding to #{local_address.inspect}"
|
126
|
+
|
127
|
+
socket = ::Socket.new(local_address.afamily, local_address.socktype, protocol)
|
128
|
+
|
129
|
+
socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true)
|
130
|
+
socket.bind(local_address.to_sockaddr)
|
131
|
+
|
132
|
+
wrapper = self.new(socket, task.reactor)
|
133
|
+
|
134
|
+
if block_given?
|
135
|
+
begin
|
136
|
+
return yield(wrapper, task)
|
137
|
+
ensure
|
138
|
+
wrapper.close
|
139
|
+
end
|
140
|
+
else
|
141
|
+
return wrapper
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Bind to a local address and accept connections in a loop.
|
146
|
+
def self.accept(*args, backlog: SOMAXCONN, &block)
|
147
|
+
bind(*args) do |server, task|
|
148
|
+
server.listen(backlog) if backlog
|
149
|
+
|
150
|
+
server.accept_each(task: task, &block)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class IPSocket < BasicSocket
|
156
|
+
wraps ::IPSocket, :addr, :peeraddr
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'socket'
|
22
|
+
|
23
|
+
module Async
|
24
|
+
module IO
|
25
|
+
# Asynchronous TCP socket wrapper.
|
26
|
+
class TCPSocket < IPSocket
|
27
|
+
wraps ::TCPSocket
|
28
|
+
end
|
29
|
+
|
30
|
+
# Asynchronous TCP server wrappper.
|
31
|
+
class TCPServer < TCPSocket
|
32
|
+
wraps ::TCPServer, :listen
|
33
|
+
|
34
|
+
include ServerSocket
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'socket'
|
22
|
+
|
23
|
+
module Async
|
24
|
+
module IO
|
25
|
+
# Asynchronous UDP socket wrapper.
|
26
|
+
class UDPSocket < IPSocket
|
27
|
+
wraps ::UDPSocket, :bind
|
28
|
+
|
29
|
+
# We pass `send` through directly, but in theory it might block. Internally, it uses sendto.
|
30
|
+
def_delegators :@io, :send
|
31
|
+
|
32
|
+
# This function is so fucked. Why does `UDPSocket#recvfrom` return the remote address as an array, but `Socket#recfrom` return it as an `Addrinfo`? You should prefer `recvmsg`.
|
33
|
+
wrap_blocking_method :recvfrom, :recvfrom_nonblock
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'socket'
|
22
|
+
|
23
|
+
module Async
|
24
|
+
module IO
|
25
|
+
class UNIXSocket < BasicSocket
|
26
|
+
wraps ::UNIXSocket, :path, :addr, :peeraddr
|
27
|
+
|
28
|
+
# TODO Currently unimplemented.
|
29
|
+
# , :send_io, :recv_io
|
30
|
+
end
|
31
|
+
|
32
|
+
class UNIXServer < UNIXSocket
|
33
|
+
wraps ::UNIXServer
|
34
|
+
|
35
|
+
def accept
|
36
|
+
peer = async_send(:accept_nonblock)
|
37
|
+
|
38
|
+
if block_given?
|
39
|
+
wrapper = UNIXSocket.new(peer, self.reactor)
|
40
|
+
|
41
|
+
begin
|
42
|
+
yield wrapper
|
43
|
+
ensure
|
44
|
+
wrapper.close
|
45
|
+
end
|
46
|
+
else
|
47
|
+
return UNIXSocket.new(peer, self.reactor)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
module Async
|
22
|
+
module IO
|
23
|
+
VERSION = "0.1.0"
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative '../socket'
|
22
|
+
|
23
|
+
module Async
|
24
|
+
module IO
|
25
|
+
module Wrap
|
26
|
+
module TCPServer
|
27
|
+
def self.new(*args)
|
28
|
+
return ::TCPServer.new(*args) unless Task.current?
|
29
|
+
|
30
|
+
case args.size
|
31
|
+
when 2
|
32
|
+
local_address = Async::IO::Address.tcp(*args)
|
33
|
+
when 1
|
34
|
+
local_address = Async::IO::Address.tcp("0.0.0.0", *args)
|
35
|
+
else
|
36
|
+
raise ArgumentError, "TCPServer.new([hostname], port)"
|
37
|
+
end
|
38
|
+
|
39
|
+
return Async::IO::Socket.bind(local_address)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module TCPSocket
|
44
|
+
def self.new(remote_host, remote_port, local_host=nil, local_port=nil)
|
45
|
+
return ::TCPSocket.new(remote_host, remote_port, local_host, local_port) unless Task.current?
|
46
|
+
|
47
|
+
remote_address = Async::IO::Address.tcp(remote_host, remote_port)
|
48
|
+
|
49
|
+
if local_host && local_port
|
50
|
+
local_address = Async::IO::Address.tcp(local_host, local_port)
|
51
|
+
end
|
52
|
+
|
53
|
+
return Async::IO::Socket.connect(remote_address, local_address)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.open(*args)
|
57
|
+
self.new(*args)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'async/io/address'
|
22
|
+
|
23
|
+
RSpec.describe Async::IO::Address do
|
24
|
+
include_context Async::RSpec::Reactor
|
25
|
+
|
26
|
+
describe Async::IO::Address.new([:tcp, '0.0.0.0', 1234]) do
|
27
|
+
it "should be a tcp binding" do
|
28
|
+
expect(subject.socktype).to be == ::Socket::SOCK_STREAM
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should generate valid address" do
|
32
|
+
expect(subject).to be == Addrinfo.tcp('0.0.0.0', 1234)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe Async::IO::Address.new(TCPServer.new('0.0.0.0', 1234)) do
|
37
|
+
it "should be a tcp binding" do
|
38
|
+
expect(subject.socktype).to be == ::Socket::SOCK_STREAM
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should generate valid address" do
|
42
|
+
expect(subject).to be == Addrinfo.tcp('0.0.0.0', 1234)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'async/io'
|
22
|
+
|
23
|
+
RSpec.describe "echo client/server" do
|
24
|
+
include_context Async::RSpec::Reactor
|
25
|
+
|
26
|
+
let(:server_address) {Async::IO::Address.new([:tcp, '0.0.0.0', 9000])}
|
27
|
+
|
28
|
+
def echo_server(server_address)
|
29
|
+
Async::Reactor.run do |task|
|
30
|
+
# This is a synchronous block within the current task:
|
31
|
+
Async::IO::Socket.accept(server_address) do |client|
|
32
|
+
# This is an asynchronous block within the current reactor:
|
33
|
+
data = client.read(512)
|
34
|
+
|
35
|
+
# This produces out-of-order responses.
|
36
|
+
task.sleep(rand * 0.01)
|
37
|
+
|
38
|
+
client.write(data)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def echo_client(server_address, data, responses)
|
44
|
+
Async::Reactor.run do |task|
|
45
|
+
Async::IO::Socket.connect(server_address) do |peer|
|
46
|
+
result = peer.write(data)
|
47
|
+
|
48
|
+
message = peer.read(512)
|
49
|
+
|
50
|
+
responses << message
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
let(:repeats) {10}
|
56
|
+
|
57
|
+
it "should echo several messages" do
|
58
|
+
server = echo_server(server_address)
|
59
|
+
responses = []
|
60
|
+
|
61
|
+
tasks = repeats.times.collect do |i|
|
62
|
+
echo_client(server_address, "Hello World #{i}", responses)
|
63
|
+
end
|
64
|
+
|
65
|
+
# task.reactor.print_hierarchy
|
66
|
+
|
67
|
+
tasks.each(&:wait)
|
68
|
+
server.stop
|
69
|
+
|
70
|
+
expect(responses.count).to be repeats
|
71
|
+
end
|
72
|
+
end
|