lib/socketry/udp/socket.rb in socketry-0.3.0 vs lib/socketry/udp/socket.rb in socketry-0.4.0
- old
+ new
@@ -5,126 +5,181 @@
module UDP
# User Datagram Protocol sockets
class Socket
include Socketry::Timeout
- attr_reader :read_timeout, :write_timeout, :resolver, :socket_class
+ attr_reader :addr_family, :read_timeout, :write_timeout, :resolver, :socket_class
# Create a UDP socket matching the given socket's address family
#
- # @param remote_addr [String] address to connect/bind to
+ # @param remote_addr [String] Address to connect/bind to
+ # @param resolver [Object] Resolver object to use for resolving DNS names
+ #
# @return [Socketry::UDP::Socket]
def self.from_addr(remote_addr, resolver: Socketry::Resolver::DEFAULT_RESOLVER)
addr = resolver.resolve(remote_addr)
- if addr.ipv4?
- new(family: :ipv4)
- elsif addr.ipv6?
- new(family: :ipv6)
+ if addr.ipv4? then new(addr_family: :ipv4)
+ elsif addr.ipv6? then new(addr_family: :ipv6)
else raise Socketry::AddressError, "unsupported IP address family: #{addr}"
end
end
- # Bind to the given address and port
+ # Create a UDP server bound to the given address and port
#
+ # @param local_addr [String] Local DNS name or IP address to listen on
+ # @param local_port [Fixnum] Local UDP port to listen on
+ # @param resolver [Object] Resolver object to use for resolving DNS names
+ #
# @return [Socketry::UDP::Socket]
- def self.bind(remote_addr, remote_port, resolver: Socketry::Resolver::DEFAULT_RESOLVER)
- from_addr(remote_addr, resolver: resolver).bind(remote_addr, remote_port)
+ def self.bind(local_addr, local_port, resolver: Socketry::Resolver::DEFAULT_RESOLVER)
+ from_addr(local_addr, resolver: resolver).bind(local_addr, local_port)
end
# Connect to the given address and port
#
+ # @param remote_addr [String] DNS name or IP address of the host to connect to
+ # @param remote_port [Fixnum] UDP port to connect to
+ # @param resolver [Object] Resolver object to use for resolving DNS names
+ #
# @return [Socketry::UDP::Socket]
def self.connect(remote_addr, remote_port, resolver: Socketry::Resolver::DEFAULT_RESOLVER)
from_addr(remote_addr, resolver: resolver).connect(remote_addr, remote_port)
end
# Create a new UDP socket
#
+ # @param addr_family [:ipv4, :ipv6] (default :ipv4) address family for this socket
+ # @param read_timeout [Numeric] Seconds to wait before an uncompleted read errors
+ # @param write_timeout [Numeric] Seconds to wait before an uncompleted write errors
+ # @param timer [Object] Time interval object to use for measuring timeouts
+ # @param resolver [Object] Resolver object to use for resolving DNS names
+ # @param socket_class [Object] Underlying socket class which implements I/O ops
+ #
+ # @raise [ArgumentError] an invalid argument was given
+ #
# @return [Socketry::UDP::Socket]
def initialize(
- family: :ipv4,
+ addr_family: :ipv4,
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: ::UDPSocket
)
- case family
- when :ipv4
- @address_family = ::Socket::AF_INET
- when :ipv6
- @address_family = ::Socket::AF_INET6
- when ::Socket::AF_INET, ::Socket::AF_INET6
- @address_family = address_family
- else raise ArgumentError, "invalid address family: #{address_family.inspect}"
- end
+ @addr_family = case addr_family
+ when :ipv4 then ::Socket::AF_INET
+ when :ipv6 then ::Socket::AF_INET6
+ when ::Socket::AF_INET, ::Socket::AF_INET6 then addr_family
+ else raise ArgumentError, "invalid address family: #{addr_family.inspect}"
+ end
- @socket = socket_class.new(@address_family)
+ @socket = socket_class.new(@addr_family)
@read_timeout = read_timeout
@write_timeout = write_timeout
@resolver = resolver
start_timer(timer)
end
- # Bind to the given address and port
+ # Start a UDP server bound to a particular address and port
#
+ # @param local_addr [String] Local DNS name or IP address to listen on
+ # @param local_port [Fixnum] Local UDP port to listen on
+ #
# @return [self]
- def bind(remote_addr, remote_port)
- @socket.bind(@resolver.resolve(remote_addr), remote_port)
+ def bind(local_addr, local_port)
+ @socket.bind(@resolver.resolve(local_addr).to_s, local_port)
self
+ rescue Errno::EADDRINUSE => ex
+ raise AddressInUseError, ex.message, ex.backtrace
rescue => ex
- # TODO: more specific exceptions
raise Socketry::Error, ex.message, ex.backtrace
end
- # Create a new UDP socket
+ # Make a UDP client connection to the given address and port
#
+ # @param remote_addr [String] DNS name or IP address of the host to connect to
+ # @param remote_port [Fixnum] UDP port to connect to
+ #
# @return [self]
def connect(remote_addr, remote_port)
- @socket.connect(@resolver.resolve(remote_addr), remote_port)
+ @socket.connect(@resolver.resolve(remote_addr).to_s, remote_port)
self
rescue => ex
# TODO: more specific exceptions
raise Socketry::Error, ex.message, ex.backtrace
end
# Perform a non-blocking receive
#
- # @return [String, :wait_readable] received packet or indication to wait
+ # @param maxlen [Fixnum] Maximum packet length to receive
+ #
+ # @return [Socketry::UDP::Datagram, :wait_readable] Received datagram or indication to wait
def recvfrom_nonblock(maxlen)
- @socket.recvfrom_nonblock(maxlen)
+ Socketry::UDP::Datagram.new(*@socket.recvfrom_nonblock(maxlen))
rescue ::IO::WaitReadable
:wait_readable
rescue => ex
# TODO: more specific exceptions
raise Socketry::Error, ex.message, ex.backtrace
end
# Perform a blocking receive
#
- # @return [String] received data
+ # @param maxlen [Fixnum] Maximum packet length to receive
+ # @param timeout [Numeric] Number of seconds to wait for recvfrom operation to complete
+ #
+ # @return [String] Received data
def recvfrom(maxlen, timeout: @read_timeout)
set_timeout(timeout)
begin
while (result = recvfrom_nonblock(maxlen)) == :wait_readable
next if @socket.wait_readable(time_remaining(timeout))
raise Socketry::TimeoutError, "recvfrom timed out after #{timeout} seconds"
end
ensure
- clear_timeout(imeout)
+ clear_timeout(timeout)
end
result
end
- # Send data to the given host and port
- def send(msg, host:, port:)
- @socket.send(msg, 0, @resolver.resolve(host), port)
+ # Send a UDP packet to a remote host
+ #
+ # @param msg [String] Data to write to the remote host/port
+ # @param host [String] Remote host to send data to. May be omitted if `connect` was called previously
+ # @param port [Fixnum] UDP port to send data to. May be omitted if `connect` was called previously
+ #
+ # @return [Fixum] Number of bytes sent
+ def send(msg, host: nil, port: nil)
+ host = @resolver.resolve(host).to_s if host
+ if host || port
+ @socket.send(msg, 0, host, port)
+ else
+ @socket.send(msg, 0)
+ end
rescue => ex
# TODO: more specific exceptions
raise Socketry::Error, ex.message, ex.backtrace
+ end
+
+ # Close the socket
+ #
+ # @return [true, false] true if the socket was open, false if closed
+ def close
+ return false if closed?
+ @socket.close
+ true
+ ensure
+ @socket = nil
+ end
+
+ # Is the socket closed?
+ #
+ # @return [true, false] do we locally think the socket is closed?
+ def closed?
+ @socket.nil?
end
end
end
end