Class: Socketry::TCP::Socket
- Inherits:
-
Object
- Object
- Socketry::TCP::Socket
- Includes:
- Socketry::Timeout
- Defined in:
- lib/socketry/tcp/socket.rb
Overview
Transmission Control Protocol sockets: Provide stream-like semantics
Direct Known Subclasses
Constant Summary
Constants included from Socketry::Timeout
Socketry::Timeout::DEFAULT_TIMEOUTS, Socketry::Timeout::DEFAULT_TIMER
Instance Attribute Summary collapse
-
#local_addr ⇒ Object
readonly
Returns the value of attribute local_addr.
-
#local_port ⇒ Object
readonly
Returns the value of attribute local_port.
-
#read_timeout ⇒ Object
readonly
Returns the value of attribute read_timeout.
-
#remote_addr ⇒ Object
readonly
Returns the value of attribute remote_addr.
-
#remote_port ⇒ Object
readonly
Returns the value of attribute remote_port.
-
#resolver ⇒ Object
readonly
Returns the value of attribute resolver.
-
#socket_class ⇒ Object
readonly
Returns the value of attribute socket_class.
-
#write_timeout ⇒ Object
readonly
Returns the value of attribute write_timeout.
Class Method Summary collapse
-
.connect(remote_addr, remote_port, **args) ⇒ Socketry::TCP::Socket
Create a Socketry::TCP::Socket with the default options, then connect to the given host.
Instance Method Summary collapse
-
#close ⇒ true, false
Close the socket.
-
#closed? ⇒ true, false
Is the socket closed?.
-
#connect(remote_addr, remote_port, local_addr: nil, local_port: nil, timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:connect]) ⇒ self
Connect to a remote host.
-
#from_socket(socket) ⇒ Object
Wrap a Ruby/low-level socket in an Socketry::TCP::Socket.
-
#initialize(read_timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:read], write_timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:write], timer: Socketry::Timeout::DEFAULT_TIMER.new, resolver: Socketry::Resolver::DEFAULT_RESOLVER, socket_class: ::Socket) ⇒ Socketry::TCP::Socket
constructor
Create an unconnected Socketry::TCP::Socket.
-
#nodelay ⇒ true, false
Check whether Nagle’s algorithm has been disabled.
-
#nodelay=(flag) ⇒ Object
Disable or enable Nagle’s algorithm.
-
#read(size, outbuf: String.new, timeout: @write_timeout) ⇒ String, :eof
Read all of the data in a given string to a socket unless timeout or EOF.
-
#read_nonblock(size, outbuf: nil) ⇒ String, :wait_readable
Perform a non-blocking read operation.
-
#readpartial(size, outbuf: nil, timeout: @read_timeout) ⇒ String, :eof
Read a partial amounth of data, blocking until it becomes available.
-
#reconnect(timeout: ) ⇒ Object
Re-establish a lost TCP connection.
-
#to_io ⇒ IO
Return a raw Ruby I/O object.
-
#write(data, timeout: @write_timeout) ⇒ Fixnum
Write all of the data in a given string to a socket unless timeout or EOF.
-
#write_nonblock(data) ⇒ Fixnum, :wait_writable
Perform a non-blocking write operation.
-
#writepartial(data, timeout: @write_timeout) ⇒ Fixnum, :eof
Write a partial amounth of data, blocking until it’s completed.
Methods included from Socketry::Timeout
#clear_timeout, #lifetime, #set_timeout, #start_timer, #time_remaining
Constructor Details
#initialize(read_timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:read], write_timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:write], timer: Socketry::Timeout::DEFAULT_TIMER.new, resolver: Socketry::Resolver::DEFAULT_RESOLVER, socket_class: ::Socket) ⇒ Socketry::TCP::Socket
Create an unconnected Socketry::TCP::Socket
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/socketry/tcp/socket.rb', line 31 def initialize( read_timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:read], write_timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:write], timer: Socketry::Timeout::DEFAULT_TIMER.new, resolver: Socketry::Resolver::DEFAULT_RESOLVER, socket_class: ::Socket ) @read_timeout = read_timeout @write_timeout = write_timeout @socket_class = socket_class @resolver = resolver @family = nil @socket = nil @remote_addr = nil @remote_port = nil @local_addr = nil @local_port = nil start_timer(timer) end |
Instance Attribute Details
#local_addr ⇒ Object (readonly)
Returns the value of attribute local_addr.
10 11 12 |
# File 'lib/socketry/tcp/socket.rb', line 10 def local_addr @local_addr end |
#local_port ⇒ Object (readonly)
Returns the value of attribute local_port.
10 11 12 |
# File 'lib/socketry/tcp/socket.rb', line 10 def local_port @local_port end |
#read_timeout ⇒ Object (readonly)
Returns the value of attribute read_timeout.
11 12 13 |
# File 'lib/socketry/tcp/socket.rb', line 11 def read_timeout @read_timeout end |
#remote_addr ⇒ Object (readonly)
Returns the value of attribute remote_addr.
10 11 12 |
# File 'lib/socketry/tcp/socket.rb', line 10 def remote_addr @remote_addr end |
#remote_port ⇒ Object (readonly)
Returns the value of attribute remote_port.
10 11 12 |
# File 'lib/socketry/tcp/socket.rb', line 10 def remote_port @remote_port end |
#resolver ⇒ Object (readonly)
Returns the value of attribute resolver.
11 12 13 |
# File 'lib/socketry/tcp/socket.rb', line 11 def resolver @resolver end |
#socket_class ⇒ Object (readonly)
Returns the value of attribute socket_class.
11 12 13 |
# File 'lib/socketry/tcp/socket.rb', line 11 def socket_class @socket_class end |
#write_timeout ⇒ Object (readonly)
Returns the value of attribute write_timeout.
11 12 13 |
# File 'lib/socketry/tcp/socket.rb', line 11 def write_timeout @write_timeout end |
Class Method Details
.connect(remote_addr, remote_port, **args) ⇒ Socketry::TCP::Socket
Create a Socketry::TCP::Socket with the default options, then connect to the given host.
19 20 21 |
# File 'lib/socketry/tcp/socket.rb', line 19 def self.connect(remote_addr, remote_port, **args) new.connect(remote_addr, remote_port, **args) end |
Instance Method Details
#close ⇒ true, false
Close the socket
302 303 304 305 306 307 308 |
# File 'lib/socketry/tcp/socket.rb', line 302 def close return false if closed? @socket.close true ensure @socket = nil end |
#closed? ⇒ true, false
Is the socket closed?
This method returns the local connection state. However, it’s possible the remote side has closed the connection, so it’s not actually possible to actually know if the socket is actually still open without reading from or writing to it. It’s sort of like the Heisenberg uncertainty principle of sockets.
319 320 321 |
# File 'lib/socketry/tcp/socket.rb', line 319 def closed? @socket.nil? end |
#connect(remote_addr, remote_port, local_addr: nil, local_port: nil, timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:connect]) ⇒ self
Connect to a remote host
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/socketry/tcp/socket.rb', line 66 def connect( remote_addr, remote_port, local_addr: nil, local_port: nil, timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:connect] ) ensure_disconnected @remote_addr = remote_addr @remote_port = remote_port @local_addr = local_addr @local_port = local_port begin set_timeout(timeout) remote_addr = @resolver.resolve(remote_addr, timeout: time_remaining(timeout)) local_addr = @resolver.resolve(local_addr, timeout: time_remaining(timeout)) if local_addr raise ArgumentError, "expected IPAddr from resolver, got #{remote_addr.class}" unless remote_addr.is_a?(IPAddr) if remote_addr.ipv4? @family = ::Socket::AF_INET elsif remote_addr.ipv6? @family = ::Socket::AF_INET6 else raise Socketry::AddressError, "unsupported IP address family: #{remote_addr}" end socket = @socket_class.new(@family, ::Socket::SOCK_STREAM, 0) socket.bind Addrinfo.tcp(local_addr.to_s, local_port) if local_addr remote_sockaddr = ::Socket.sockaddr_in(remote_port, remote_addr.to_s) # Note: `exception: false` for Socket#connect_nonblock is only supported in Ruby 2.3+ begin socket.connect_nonblock(remote_sockaddr) rescue Errno::ECONNREFUSED => ex raise Socketry::ConnectionRefusedError, "connection to #{remote_addr}:#{remote_port} refused", ex.backtrace rescue Errno::EINPROGRESS, Errno::EALREADY # Earlier JRuby 9.x versions do not seem to correctly support Socket#wait_writable in this case # Newer versions seem to behave correctly retry if IO.select(nil, [socket], nil, time_remaining(timeout)) socket.close raise Socketry::TimeoutError, "connection to #{remote_addr}:#{remote_port} timed out" rescue Errno::EISCONN # Sometimes raised when we've connected successfully end @socket = socket ensure clear_timeout(timeout) end self end |
#from_socket(socket) ⇒ Object
Wrap a Ruby/low-level socket in an Socketry::TCP::Socket
135 136 137 138 139 140 |
# File 'lib/socketry/tcp/socket.rb', line 135 def from_socket(socket) ensure_disconnected raise TypeError, "expected #{@socket_class}, got #{socket.class}" unless socket.is_a?(@socket_class) @socket = socket self end |
#nodelay ⇒ true, false
Check whether Nagle’s algorithm has been disabled
278 279 280 281 |
# File 'lib/socketry/tcp/socket.rb', line 278 def nodelay ensure_connected @socket.getsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY).int.nonzero? end |
#nodelay=(flag) ⇒ Object
Disable or enable Nagle’s algorithm
286 287 288 289 |
# File 'lib/socketry/tcp/socket.rb', line 286 def nodelay=(flag) ensure_connected @socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, flag ? 1 : 0) end |
#read(size, outbuf: String.new, timeout: @write_timeout) ⇒ String, :eof
Read all of the data in a given string to a socket unless timeout or EOF
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/socketry/tcp/socket.rb', line 193 def read(size, outbuf: String.new, timeout: @write_timeout) outbuf.clear deadline = lifetime + timeout if timeout begin until outbuf.size == size time_remaining = deadline - lifetime if deadline raise Socketry::TimeoutError, "read timed out after #{timeout} seconds" if timeout && time_remaining <= 0 chunk = readpartial(size - outbuf.size, timeout: time_remaining) return :eof if chunk == :eof outbuf << chunk end end outbuf end |
#read_nonblock(size, outbuf: nil) ⇒ String, :wait_readable
Perform a non-blocking read operation
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/socketry/tcp/socket.rb', line 148 def read_nonblock(size, outbuf: nil) ensure_connected case outbuf when String @socket.read_nonblock(size, outbuf, exception: false) when NilClass @socket.read_nonblock(size, exception: false) else raise TypeError, "unexpected outbuf class: #{outbuf.class}" end rescue IO::WaitReadable # Some buggy Rubies continue to raise this exception :wait_readable rescue IOError => ex raise Socketry::Error, ex., ex.backtrace end |
#readpartial(size, outbuf: nil, timeout: @read_timeout) ⇒ String, :eof
Read a partial amounth of data, blocking until it becomes available
171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/socketry/tcp/socket.rb', line 171 def readpartial(size, outbuf: nil, timeout: @read_timeout) set_timeout(timeout) begin while (result = read_nonblock(size, outbuf: outbuf)) == :wait_readable next if @socket.wait_readable(time_remaining(timeout)) raise TimeoutError, "read timed out after #{timeout} seconds" end ensure clear_timeout(timeout) end result || :eof end |
#reconnect(timeout: ) ⇒ Object
Re-establish a lost TCP connection
126 127 128 129 130 |
# File 'lib/socketry/tcp/socket.rb', line 126 def reconnect(timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:connect]) ensure_disconnected raise StateError, "can't reconnect: never completed initial connection" unless @remote_addr connect(@remote_addr, @remote_port, local_addr: @local_addr, local_port: @local_port, timeout: timeout) end |
#to_io ⇒ IO
Return a raw Ruby I/O object
294 295 296 297 |
# File 'lib/socketry/tcp/socket.rb', line 294 def to_io ensure_connected ::IO.try_convert(@socket) end |
#write(data, timeout: @write_timeout) ⇒ Fixnum
Write all of the data in a given string to a socket unless timeout or EOF
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/socketry/tcp/socket.rb', line 254 def write(data, timeout: @write_timeout) total_written = data.size deadline = lifetime + timeout if timeout begin until data.empty? time_remaining = deadline - lifetime if deadline raise Socketry::TimeoutError, "write timed out after #{timeout} seconds" if timeout && time_remaining <= 0 bytes_written = writepartial(data, timeout: time_remaining) return :eof if bytes_written == :eof break if bytes_written == data.bytesize data = data.byteslice(bytes_written..-1) end end total_written end |
#write_nonblock(data) ⇒ Fixnum, :wait_writable
Perform a non-blocking write operation
217 218 219 220 221 222 223 224 225 |
# File 'lib/socketry/tcp/socket.rb', line 217 def write_nonblock(data) ensure_connected @socket.write_nonblock(data, exception: false) rescue IO::WaitWriteable # Some buggy Rubies continue to raise this exception :wait_writable rescue IOError => ex raise Socketry::Error, ex., ex.backtrace end |
#writepartial(data, timeout: @write_timeout) ⇒ Fixnum, :eof
Write a partial amounth of data, blocking until it’s completed
233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/socketry/tcp/socket.rb', line 233 def writepartial(data, timeout: @write_timeout) set_timeout(timeout) begin while (result = write_nonblock(data)) == :wait_writable next if @socket.wait_writable(time_remaining(timeout)) raise TimeoutError, "write timed out after #{timeout} seconds" end ensure clear_timeout(timeout) end result || :eof end |