socketry 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c144331b5b09954959008e255335d5ac43c70be3
4
- data.tar.gz: 704cb38c89f0cc0779aaeba5859877f189f5664c
3
+ metadata.gz: ca5c2f89cca095c9a9512b02c3dff7d7af615af2
4
+ data.tar.gz: 5d4ccc55cb34dcea0bb63986882c52195a1dabc3
5
5
  SHA512:
6
- metadata.gz: f49d7c5ee328c0c6b7f6366aca43d147a56045b1b1f3dca05219caef2216e791454585e512a7195f61dc1fb767230a2dde82f5130e1a174474d26b6ad0fbbabb
7
- data.tar.gz: 7147ec4e25e1d4af36d27452be851adbec957ce65747f95ce873e91936ec962b3576d62b0f171304079dad998030289874a55267682c7842debb7fe7d7ac0413
6
+ metadata.gz: c7cfa8860c9e4aa4f0685d8d707f433d1d98ecdf56bad86146d8347b5480492ad51ac03e1d18035899e1ac2457f64b77f0822b91a7ca8e24af85a0262ffe1a27
7
+ data.tar.gz: b63f66204999a96fe018d168d9448de66d1ad97b1793c145af5b41cd4a04e806fce9cf1d84901d99872ffbf0ec9cbbd0cafded0777db4ca14e9a5ea904fff3d9
@@ -1 +1 @@
1
- 2.3.1
1
+ 2.3.3
@@ -5,8 +5,8 @@ bundler_args: --without development doc
5
5
 
6
6
  rvm:
7
7
  - 2.2
8
- - 2.3.1
9
- - jruby-9.0.5.0
8
+ - 2.3.3
9
+ - jruby-9.1.6.0
10
10
 
11
11
  matrix:
12
12
  fast_finish: true
data/CHANGES.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 0.4.0 (2016-11-25)
2
+
3
+ * Specs and bugfixes for SSL sockets
4
+ * Specs and bugfixes for UDP sockets
5
+ * Add Socketry::UDP::Datagram class
6
+ * Add Socketry::AddressInUseError exception
7
+
1
8
  ## 0.3.0 (2016-09-24)
2
9
 
3
10
  * Implement Socketry::TCP::Socket#read and #write
data/README.md CHANGED
@@ -11,13 +11,9 @@
11
11
  [license-image]: https://img.shields.io/badge/license-MIT-blue.svg
12
12
  [license-link]: https://github.com/socketry/socketry/blob/master/LICENSE.txt
13
13
 
14
- High-level wrappers for Ruby sockets with advanced thread-safe timeout support.
14
+ High-level Ruby socket library with support for TCP, UDP, and SSL sockets.
15
15
 
16
- **Does not require Celluloid!** Socketry provides sockets with thread-safe
17
- timeout support that can be used with any multithreaded Ruby app. That said,
18
- Socketry can also be used to provide asynchronous I/O with [Celluloid::IO].
19
-
20
- [Celluloid::IO]: https://github.com/celluloid/celluloid-io
16
+ Implements thread-safe timeouts using asynchronous I/O and high-precision monotonic timers.
21
17
 
22
18
  ## Motivation
23
19
 
@@ -21,4 +21,5 @@ require "socketry/tcp/server"
21
21
  require "socketry/tcp/socket"
22
22
  require "socketry/ssl/server"
23
23
  require "socketry/ssl/socket"
24
+ require "socketry/udp/datagram"
24
25
  require "socketry/udp/socket"
@@ -10,6 +10,9 @@ module Socketry
10
10
  # Invalid address
11
11
  AddressError = Class.new(Socketry::Error)
12
12
 
13
+ # Address is already in use
14
+ AddressInUseError = Class.new(Socketry::Error)
15
+
13
16
  # Timeouts performing an I/O operation
14
17
  TimeoutError = Class.new(Socketry::Error)
15
18
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "resolv"
4
+
3
5
  module Socketry
4
6
  module Resolver
5
7
  # Pure Ruby DNS resolver provided by the standard library
@@ -26,9 +26,10 @@ module Socketry
26
26
  raise TypeError, "expected Hash, got #{ssl_params.class}" if ssl_params && !ssl_params.is_a?(Hash)
27
27
 
28
28
  @ssl_socket_class = ssl_socket_class
29
+
29
30
  @ssl_context = ssl_context
30
31
  @ssl_context.set_params(ssl_params) if ssl_params && !ssl_params.empty?
31
- @ssl_context.freeze
32
+
32
33
  @ssl_socket = nil
33
34
 
34
35
  super(**args)
@@ -41,7 +42,8 @@ module Socketry
41
42
  # @param local_addr [String] DNS name or IP address to bind to locally
42
43
  # @param local_port [Fixnum] Local TCP port to bind to
43
44
  # @param timeout [Numeric] Number of seconds to wait before aborting connect
44
- # @param socket_class [Class] Custom low-level socket class
45
+ # @param enable_sni [true, false] (default: true) Enables Server Name Indication (SNI)
46
+ # @param verify_hostname [true, false] (default: true) Ensure server's hostname matches cert
45
47
  # @raise [Socketry::AddressError] an invalid address was given
46
48
  # @raise [Socketry::TimeoutError] connect operation timed out
47
49
  # @raise [Socketry::SSL::Error] an error occurred negotiating an SSL connection
@@ -52,12 +54,13 @@ module Socketry
52
54
  local_addr: nil,
53
55
  local_port: nil,
54
56
  timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:connect],
57
+ enable_sni: true,
55
58
  verify_hostname: true
56
59
  )
57
60
  super(remote_addr, remote_port, local_addr: local_addr, local_port: local_port, timeout: timeout)
58
61
 
59
62
  @ssl_socket = OpenSSL::SSL::SSLSocket.new(@socket, @ssl_context)
60
- @ssl_socket.hostname = remote_addr
63
+ @ssl_socket.hostname = remote_addr if enable_sni
61
64
 
62
65
  begin
63
66
  @ssl_socket.connect_nonblock
@@ -110,23 +113,13 @@ module Socketry
110
113
  # @raise [Socketry::Error] an I/O operation failed
111
114
  # @return [String, :wait_readable] data read, or :wait_readable if operation would block
112
115
  def read_nonblock(size, outbuf: nil)
113
- ensure_connected
114
116
  case outbuf
115
117
  when String
116
- @ssl_socket.read_nonblock(size, outbuf, exception: false)
118
+ perform { @ssl_socket.read_nonblock(size, outbuf, exception: false) }
117
119
  when NilClass
118
- @ssl_socket.read_nonblock(size, exception: false)
120
+ perform { @ssl_socket.read_nonblock(size, exception: false) }
119
121
  else raise TypeError, "unexpected outbuf class: #{outbuf.class}"
120
122
  end
121
- # Some buggy Rubies continue to raise exceptions in these cases
122
- rescue IO::WaitReadable
123
- :wait_readable
124
- # Due to SSL, we may need to write to complete a read (e.g. renegotiation)
125
- rescue IO::WaitWritable
126
- :wait_writable
127
- rescue => ex
128
- # TODO: more specific exceptions
129
- raise Socketry::Error, ex.message, ex.backtrace
130
123
  end
131
124
 
132
125
  # Perform a non-blocking write operation
@@ -135,26 +128,33 @@ module Socketry
135
128
  # @raise [Socketry::Error] an I/O operation failed
136
129
  # @return [Fixnum, :wait_writable] number of bytes written, or :wait_writable if op would block
137
130
  def write_nonblock(data)
131
+ perform { @ssl_socket.write_nonblock(data, exception: false) }
132
+ end
133
+
134
+ # Close the socket
135
+ #
136
+ # @return [true, false] true if the socket was open, false if closed
137
+ def close
138
+ @ssl_socket.close rescue nil
139
+ super
140
+ end
141
+
142
+ private
143
+
144
+ # Perform a non-blocking I/O operation
145
+ def perform
138
146
  ensure_connected
139
- @ssl_socket.write_nonblock(data, exception: false)
147
+ yield
140
148
  # Some buggy Rubies continue to raise this exception
141
- rescue IO::WaitWriteable
149
+ rescue IO::WaitWritable
142
150
  :wait_writable
143
- # Due to SSL, we may need to write to complete a read (e.g. renegotiation)
151
+ # Due to SSL, we may need to write to complete a read (e.g. handshaking, renegotiation)
144
152
  rescue IO::WaitReadable
145
153
  :wait_readable
146
154
  rescue => ex
147
155
  # TODO: more specific exceptions
148
156
  raise Socketry::Error, ex.message, ex.backtrace
149
157
  end
150
-
151
- # Close the socket
152
- #
153
- # @return [true, false] true if the socket was open, false if closed
154
- def close
155
- @ssl_socket.close
156
- super
157
- end
158
158
  end
159
159
  end
160
160
  end
@@ -43,6 +43,8 @@ module Socketry
43
43
  end
44
44
 
45
45
  start_timer(timer)
46
+ rescue Errno::EADDRINUSE => ex
47
+ raise AddressInUseError, ex.message, ex.backtrace
46
48
  end
47
49
 
48
50
  # Accept a connection to the server
@@ -7,7 +7,7 @@ module Socketry
7
7
  class Socket
8
8
  include Socketry::Timeout
9
9
 
10
- attr_reader :remote_addr, :remote_port, :local_addr, :local_port
10
+ attr_reader :addr_fmaily, :remote_addr, :remote_port, :local_addr, :local_port
11
11
  attr_reader :read_timeout, :write_timeout, :resolver, :socket_class
12
12
 
13
13
  # Create a Socketry::TCP::Socket with the default options, then connect
@@ -15,6 +15,7 @@ module Socketry
15
15
  #
16
16
  # @param remote_addr [String] DNS name or IP address of the host to connect to
17
17
  # @param remote_port [Fixnum] TCP port to connect to
18
+ #
18
19
  # @return [Socketry::TCP::Socket]
19
20
  def self.connect(remote_addr, remote_port, **args)
20
21
  new.connect(remote_addr, remote_port, **args)
@@ -27,6 +28,7 @@ module Socketry
27
28
  # @param timer [Object] A timekeeping object to use for measuring timeouts
28
29
  # @param resolver [Object] A resolver object to use for resolving DNS names
29
30
  # @param socket_class [Object] Underlying socket class which implements I/O ops
31
+ #
30
32
  # @return [Socketry::TCP::Socket]
31
33
  def initialize(
32
34
  read_timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:read],
@@ -41,7 +43,7 @@ module Socketry
41
43
  @socket_class = socket_class
42
44
  @resolver = resolver
43
45
 
44
- @family = nil
46
+ @addr_family = nil
45
47
  @socket = nil
46
48
 
47
49
  @remote_addr = nil
@@ -59,9 +61,10 @@ module Socketry
59
61
  # @param local_addr [String] DNS name or IP address to bind to locally
60
62
  # @param local_port [Fixnum] Local TCP port to bind to
61
63
  # @param timeout [Numeric] Number of seconds to wait before aborting connect
62
- # @param socket_class [Class] Custom low-level socket class
64
+ #
63
65
  # @raise [Socketry::AddressError] an invalid address was given
64
66
  # @raise [Socketry::TimeoutError] connect operation timed out
67
+ #
65
68
  # @return [self]
66
69
  def connect(
67
70
  remote_addr,
@@ -84,14 +87,12 @@ module Socketry
84
87
  local_addr = @resolver.resolve(local_addr, timeout: time_remaining(timeout)) if local_addr
85
88
  raise ArgumentError, "expected IPAddr from resolver, got #{remote_addr.class}" unless remote_addr.is_a?(IPAddr)
86
89
 
87
- if remote_addr.ipv4?
88
- @family = ::Socket::AF_INET
89
- elsif remote_addr.ipv6?
90
- @family = ::Socket::AF_INET6
91
- else raise Socketry::AddressError, "unsupported IP address family: #{remote_addr}"
92
- end
90
+ @addr_family = if remote_addr.ipv4? then ::Socket::AF_INET
91
+ elsif remote_addr.ipv6? then ::Socket::AF_INET6
92
+ else raise Socketry::AddressError, "unsupported IP address family: #{remote_addr}"
93
+ end
93
94
 
94
- socket = @socket_class.new(@family, ::Socket::SOCK_STREAM, 0)
95
+ socket = @socket_class.new(@addr_family, ::Socket::SOCK_STREAM, 0)
95
96
  socket.bind Addrinfo.tcp(local_addr.to_s, local_port) if local_addr
96
97
  remote_sockaddr = ::Socket.sockaddr_in(remote_port, remote_addr.to_s)
97
98
 
@@ -143,7 +144,9 @@ module Socketry
143
144
  #
144
145
  # @param size [Fixnum] number of bytes to attempt to read
145
146
  # @param outbuf [String, NilClass] an optional buffer into which data should be read
147
+ #
146
148
  # @raise [Socketry::Error] an I/O operation failed
149
+ #
147
150
  # @return [String, :wait_readable] data read, or :wait_readable if operation would block
148
151
  def read_nonblock(size, outbuf: nil)
149
152
  ensure_connected
@@ -188,7 +191,9 @@ module Socketry
188
191
  # @param size [Fixnum] number of bytes to attempt to read
189
192
  # @param outbuf [String] an output buffer to read data into
190
193
  # @param timeout [Numeric] Number of seconds to wait for read operation to complete
194
+ #
191
195
  # @raise [Socketry::Error] an I/O operation failed
196
+ #
192
197
  # @return [String, :eof] bytes read, or :eof if socket closed while reading
193
198
  def read(size, outbuf: String.new, timeout: @write_timeout)
194
199
  outbuf.clear
@@ -212,7 +217,9 @@ module Socketry
212
217
  # Perform a non-blocking write operation
213
218
  #
214
219
  # @param data [String] data to write to the socket
220
+ #
215
221
  # @raise [Socketry::Error] an I/O operation failed
222
+ #
216
223
  # @return [Fixnum, :wait_writable] number of bytes written, or :wait_writable if op would block
217
224
  def write_nonblock(data)
218
225
  ensure_connected
@@ -249,7 +256,9 @@ module Socketry
249
256
  #
250
257
  # @param data [String] data to write to the socket
251
258
  # @param timeout [Numeric] Number of seconds to wait for write operation to complete
259
+ #
252
260
  # @raise [Socketry::Error] an I/O operation failed
261
+ #
253
262
  # @return [Fixnum] number of bytes written, or :eof if socket closed during writing
254
263
  def write(data, timeout: @write_timeout)
255
264
  total_written = data.size
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Socketry
4
+ # User Datagram Protocol: "fire-and-forget" packet protocol
5
+ module UDP
6
+ # Represents a received UDP message
7
+ class Datagram
8
+ attr_reader :message, :sockaddr, :host, :addr, :port
9
+
10
+ def initialize(message, sockaddr)
11
+ @message = message
12
+ @sockaddr = sockaddr
13
+ @port = sockaddr[1]
14
+ @host = sockaddr[2]
15
+ @addr = sockaddr[3]
16
+ end
17
+
18
+ def addrinfo
19
+ addr_family = case @sockaddr[0]
20
+ when "AF_INET" then ::Socket::AF_INET
21
+ when "AF_INET6" then ::Socket::AF_INET6
22
+ else raise Socketry::AddressError, "unsupported IP address family: #{@sockaddr[0]}"
23
+ end
24
+
25
+ Addrinfo.new(@sockaddr, addr_family, ::Socket::SOCK_DGRAM)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -7,31 +7,39 @@ module Socketry
7
7
  class Socket
8
8
  include Socketry::Timeout
9
9
 
10
- attr_reader :read_timeout, :write_timeout, :resolver, :socket_class
10
+ attr_reader :addr_family, :read_timeout, :write_timeout, :resolver, :socket_class
11
11
 
12
12
  # Create a UDP socket matching the given socket's address family
13
13
  #
14
- # @param remote_addr [String] address to connect/bind to
14
+ # @param remote_addr [String] Address to connect/bind to
15
+ # @param resolver [Object] Resolver object to use for resolving DNS names
16
+ #
15
17
  # @return [Socketry::UDP::Socket]
16
18
  def self.from_addr(remote_addr, resolver: Socketry::Resolver::DEFAULT_RESOLVER)
17
19
  addr = resolver.resolve(remote_addr)
18
- if addr.ipv4?
19
- new(family: :ipv4)
20
- elsif addr.ipv6?
21
- new(family: :ipv6)
20
+ if addr.ipv4? then new(addr_family: :ipv4)
21
+ elsif addr.ipv6? then new(addr_family: :ipv6)
22
22
  else raise Socketry::AddressError, "unsupported IP address family: #{addr}"
23
23
  end
24
24
  end
25
25
 
26
- # Bind to the given address and port
26
+ # Create a UDP server bound to the given address and port
27
+ #
28
+ # @param local_addr [String] Local DNS name or IP address to listen on
29
+ # @param local_port [Fixnum] Local UDP port to listen on
30
+ # @param resolver [Object] Resolver object to use for resolving DNS names
27
31
  #
28
32
  # @return [Socketry::UDP::Socket]
29
- def self.bind(remote_addr, remote_port, resolver: Socketry::Resolver::DEFAULT_RESOLVER)
30
- from_addr(remote_addr, resolver: resolver).bind(remote_addr, remote_port)
33
+ def self.bind(local_addr, local_port, resolver: Socketry::Resolver::DEFAULT_RESOLVER)
34
+ from_addr(local_addr, resolver: resolver).bind(local_addr, local_port)
31
35
  end
32
36
 
33
37
  # Connect to the given address and port
34
38
  #
39
+ # @param remote_addr [String] DNS name or IP address of the host to connect to
40
+ # @param remote_port [Fixnum] UDP port to connect to
41
+ # @param resolver [Object] Resolver object to use for resolving DNS names
42
+ #
35
43
  # @return [Socketry::UDP::Socket]
36
44
  def self.connect(remote_addr, remote_port, resolver: Socketry::Resolver::DEFAULT_RESOLVER)
37
45
  from_addr(remote_addr, resolver: resolver).connect(remote_addr, remote_port)
@@ -39,26 +47,32 @@ module Socketry
39
47
 
40
48
  # Create a new UDP socket
41
49
  #
50
+ # @param addr_family [:ipv4, :ipv6] (default :ipv4) address family for this socket
51
+ # @param read_timeout [Numeric] Seconds to wait before an uncompleted read errors
52
+ # @param write_timeout [Numeric] Seconds to wait before an uncompleted write errors
53
+ # @param timer [Object] Time interval object to use for measuring timeouts
54
+ # @param resolver [Object] Resolver object to use for resolving DNS names
55
+ # @param socket_class [Object] Underlying socket class which implements I/O ops
56
+ #
57
+ # @raise [ArgumentError] an invalid argument was given
58
+ #
42
59
  # @return [Socketry::UDP::Socket]
43
60
  def initialize(
44
- family: :ipv4,
61
+ addr_family: :ipv4,
45
62
  read_timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:read],
46
63
  write_timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:write],
47
64
  timer: Socketry::Timeout::DEFAULT_TIMER.new,
48
65
  resolver: Socketry::Resolver::DEFAULT_RESOLVER,
49
66
  socket_class: ::UDPSocket
50
67
  )
51
- case family
52
- when :ipv4
53
- @address_family = ::Socket::AF_INET
54
- when :ipv6
55
- @address_family = ::Socket::AF_INET6
56
- when ::Socket::AF_INET, ::Socket::AF_INET6
57
- @address_family = address_family
58
- else raise ArgumentError, "invalid address family: #{address_family.inspect}"
59
- end
68
+ @addr_family = case addr_family
69
+ when :ipv4 then ::Socket::AF_INET
70
+ when :ipv6 then ::Socket::AF_INET6
71
+ when ::Socket::AF_INET, ::Socket::AF_INET6 then addr_family
72
+ else raise ArgumentError, "invalid address family: #{addr_family.inspect}"
73
+ end
60
74
 
61
- @socket = socket_class.new(@address_family)
75
+ @socket = socket_class.new(@addr_family)
62
76
  @read_timeout = read_timeout
63
77
  @write_timeout = write_timeout
64
78
  @resolver = resolver
@@ -66,22 +80,29 @@ module Socketry
66
80
  start_timer(timer)
67
81
  end
68
82
 
69
- # Bind to the given address and port
83
+ # Start a UDP server bound to a particular address and port
84
+ #
85
+ # @param local_addr [String] Local DNS name or IP address to listen on
86
+ # @param local_port [Fixnum] Local UDP port to listen on
70
87
  #
71
88
  # @return [self]
72
- def bind(remote_addr, remote_port)
73
- @socket.bind(@resolver.resolve(remote_addr), remote_port)
89
+ def bind(local_addr, local_port)
90
+ @socket.bind(@resolver.resolve(local_addr).to_s, local_port)
74
91
  self
92
+ rescue Errno::EADDRINUSE => ex
93
+ raise AddressInUseError, ex.message, ex.backtrace
75
94
  rescue => ex
76
- # TODO: more specific exceptions
77
95
  raise Socketry::Error, ex.message, ex.backtrace
78
96
  end
79
97
 
80
- # Create a new UDP socket
98
+ # Make a UDP client connection to the given address and port
99
+ #
100
+ # @param remote_addr [String] DNS name or IP address of the host to connect to
101
+ # @param remote_port [Fixnum] UDP port to connect to
81
102
  #
82
103
  # @return [self]
83
104
  def connect(remote_addr, remote_port)
84
- @socket.connect(@resolver.resolve(remote_addr), remote_port)
105
+ @socket.connect(@resolver.resolve(remote_addr).to_s, remote_port)
85
106
  self
86
107
  rescue => ex
87
108
  # TODO: more specific exceptions
@@ -90,9 +111,11 @@ module Socketry
90
111
 
91
112
  # Perform a non-blocking receive
92
113
  #
93
- # @return [String, :wait_readable] received packet or indication to wait
114
+ # @param maxlen [Fixnum] Maximum packet length to receive
115
+ #
116
+ # @return [Socketry::UDP::Datagram, :wait_readable] Received datagram or indication to wait
94
117
  def recvfrom_nonblock(maxlen)
95
- @socket.recvfrom_nonblock(maxlen)
118
+ Socketry::UDP::Datagram.new(*@socket.recvfrom_nonblock(maxlen))
96
119
  rescue ::IO::WaitReadable
97
120
  :wait_readable
98
121
  rescue => ex
@@ -102,7 +125,10 @@ module Socketry
102
125
 
103
126
  # Perform a blocking receive
104
127
  #
105
- # @return [String] received data
128
+ # @param maxlen [Fixnum] Maximum packet length to receive
129
+ # @param timeout [Numeric] Number of seconds to wait for recvfrom operation to complete
130
+ #
131
+ # @return [String] Received data
106
132
  def recvfrom(maxlen, timeout: @read_timeout)
107
133
  set_timeout(timeout)
108
134
 
@@ -112,19 +138,48 @@ module Socketry
112
138
  raise Socketry::TimeoutError, "recvfrom timed out after #{timeout} seconds"
113
139
  end
114
140
  ensure
115
- clear_timeout(imeout)
141
+ clear_timeout(timeout)
116
142
  end
117
143
 
118
144
  result
119
145
  end
120
146
 
121
- # Send data to the given host and port
122
- def send(msg, host:, port:)
123
- @socket.send(msg, 0, @resolver.resolve(host), port)
147
+ # Send a UDP packet to a remote host
148
+ #
149
+ # @param msg [String] Data to write to the remote host/port
150
+ # @param host [String] Remote host to send data to. May be omitted if `connect` was called previously
151
+ # @param port [Fixnum] UDP port to send data to. May be omitted if `connect` was called previously
152
+ #
153
+ # @return [Fixum] Number of bytes sent
154
+ def send(msg, host: nil, port: nil)
155
+ host = @resolver.resolve(host).to_s if host
156
+ if host || port
157
+ @socket.send(msg, 0, host, port)
158
+ else
159
+ @socket.send(msg, 0)
160
+ end
124
161
  rescue => ex
125
162
  # TODO: more specific exceptions
126
163
  raise Socketry::Error, ex.message, ex.backtrace
127
164
  end
165
+
166
+ # Close the socket
167
+ #
168
+ # @return [true, false] true if the socket was open, false if closed
169
+ def close
170
+ return false if closed?
171
+ @socket.close
172
+ true
173
+ ensure
174
+ @socket = nil
175
+ end
176
+
177
+ # Is the socket closed?
178
+ #
179
+ # @return [true, false] do we locally think the socket is closed?
180
+ def closed?
181
+ @socket.nil?
182
+ end
128
183
  end
129
184
  end
130
185
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Socketry
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: socketry
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Arcieri
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-09-24 00:00:00.000000000 Z
11
+ date: 2016-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hitimes
@@ -66,6 +66,7 @@ files:
66
66
  - lib/socketry/tcp/server.rb
67
67
  - lib/socketry/tcp/socket.rb
68
68
  - lib/socketry/timeout.rb
69
+ - lib/socketry/udp/datagram.rb
69
70
  - lib/socketry/udp/socket.rb
70
71
  - lib/socketry/version.rb
71
72
  - socketry.gemspec
@@ -89,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
90
  version: '0'
90
91
  requirements: []
91
92
  rubyforge_project:
92
- rubygems_version: 2.5.1
93
+ rubygems_version: 2.5.2
93
94
  signing_key:
94
95
  specification_version: 4
95
96
  summary: High-level wrappers for Ruby sockets with advanced thread-safe timeout support