Class: Socketry::UDP::Socket

Inherits:
Object
  • Object
show all
Includes:
Timeout
Defined in:
lib/socketry/udp/socket.rb

Overview

User Datagram Protocol sockets

Constant Summary

Constants included from Timeout

Timeout::DEFAULT_TIMEOUTS, Timeout::DEFAULT_TIMER

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Timeout

#clear_timeout, #lifetime, #set_timeout, #start_timer, #time_remaining

Constructor Details

#initialize(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) ⇒ Socketry::UDP::Socket

Create a new UDP socket

Parameters:

  • addr_family (:ipv4, :ipv6) (defaults to: :ipv4)

    (default :ipv4) address family for this socket

  • read_timeout (Numeric) (defaults to: Socketry::Timeout::DEFAULT_TIMEOUTS[:read])

    Seconds to wait before an uncompleted read errors

  • write_timeout (Numeric) (defaults to: Socketry::Timeout::DEFAULT_TIMEOUTS[:write])

    Seconds to wait before an uncompleted write errors

  • timer (Object) (defaults to: Socketry::Timeout::DEFAULT_TIMER.new)

    Time interval object to use for measuring timeouts

  • resolver (Object) (defaults to: Socketry::Resolver::DEFAULT_RESOLVER)

    Resolver object to use for resolving DNS names

  • socket_class (Object) (defaults to: ::UDPSocket)

    Underlying socket class which implements I/O ops

Raises:

  • (ArgumentError)

    an invalid argument was given



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/socketry/udp/socket.rb', line 60

def initialize(
  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
)
  @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(@addr_family)
  @read_timeout  = read_timeout
  @write_timeout = write_timeout
  @resolver      = resolver

  start_timer(timer)
end

Instance Attribute Details

#addr_familyObject (readonly)

Returns the value of attribute addr_family.



10
11
12
# File 'lib/socketry/udp/socket.rb', line 10

def addr_family
  @addr_family
end

#read_timeoutObject (readonly)

Returns the value of attribute read_timeout.



10
11
12
# File 'lib/socketry/udp/socket.rb', line 10

def read_timeout
  @read_timeout
end

#resolverObject (readonly)

Returns the value of attribute resolver.



10
11
12
# File 'lib/socketry/udp/socket.rb', line 10

def resolver
  @resolver
end

#socket_classObject (readonly)

Returns the value of attribute socket_class.



10
11
12
# File 'lib/socketry/udp/socket.rb', line 10

def socket_class
  @socket_class
end

#write_timeoutObject (readonly)

Returns the value of attribute write_timeout.



10
11
12
# File 'lib/socketry/udp/socket.rb', line 10

def write_timeout
  @write_timeout
end

Class Method Details

.bind(local_addr, local_port, resolver: Socketry::Resolver::DEFAULT_RESOLVER) ⇒ Socketry::UDP::Socket

Create a UDP server bound to the given address and port

Parameters:

  • local_addr (String)

    Local DNS name or IP address to listen on

  • local_port (Fixnum)

    Local UDP port to listen on

  • resolver (Object) (defaults to: Socketry::Resolver::DEFAULT_RESOLVER)

    Resolver object to use for resolving DNS names

Returns:



33
34
35
# File 'lib/socketry/udp/socket.rb', line 33

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(remote_addr, remote_port, resolver: Socketry::Resolver::DEFAULT_RESOLVER) ⇒ Socketry::UDP::Socket

Connect to the given address and port

Parameters:

  • remote_addr (String)

    DNS name or IP address of the host to connect to

  • remote_port (Fixnum)

    UDP port to connect to

  • resolver (Object) (defaults to: Socketry::Resolver::DEFAULT_RESOLVER)

    Resolver object to use for resolving DNS names

Returns:



44
45
46
# File 'lib/socketry/udp/socket.rb', line 44

def self.connect(remote_addr, remote_port, resolver: Socketry::Resolver::DEFAULT_RESOLVER)
  from_addr(remote_addr, resolver: resolver).connect(remote_addr, remote_port)
end

.from_addr(remote_addr, resolver: Socketry::Resolver::DEFAULT_RESOLVER) ⇒ Socketry::UDP::Socket

Create a UDP socket matching the given socket’s address family

Parameters:

  • remote_addr (String)

    Address to connect/bind to

  • resolver (Object) (defaults to: Socketry::Resolver::DEFAULT_RESOLVER)

    Resolver object to use for resolving DNS names

Returns:



18
19
20
21
22
23
24
# File 'lib/socketry/udp/socket.rb', line 18

def self.from_addr(remote_addr, resolver: Socketry::Resolver::DEFAULT_RESOLVER)
  addr = resolver.resolve(remote_addr)
  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

Instance Method Details

#bind(local_addr, local_port) ⇒ self

Start a UDP server bound to a particular address and port

Parameters:

  • local_addr (String)

    Local DNS name or IP address to listen on

  • local_port (Fixnum)

    Local UDP port to listen on

Returns:

  • (self)


89
90
91
92
93
94
95
96
# File 'lib/socketry/udp/socket.rb', line 89

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
  raise Socketry::Error, ex.message, ex.backtrace
end

#closetrue, false

Close the socket

Returns:

  • (true, false)

    true if the socket was open, false if closed



169
170
171
172
173
174
175
# File 'lib/socketry/udp/socket.rb', line 169

def close
  return false if closed?
  @socket.close
  true
ensure
  @socket = nil
end

#closed?true, false

Is the socket closed?

Returns:

  • (true, false)

    do we locally think the socket is closed?



180
181
182
# File 'lib/socketry/udp/socket.rb', line 180

def closed?
  @socket.nil?
end

#connect(remote_addr, remote_port) ⇒ self

Make a UDP client connection to the given address and port

Parameters:

  • remote_addr (String)

    DNS name or IP address of the host to connect to

  • remote_port (Fixnum)

    UDP port to connect to

Returns:

  • (self)


104
105
106
107
108
109
110
# File 'lib/socketry/udp/socket.rb', line 104

def connect(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

#recvfrom(maxlen, timeout: @read_timeout) ⇒ String

Perform a blocking receive

Parameters:

  • maxlen (Fixnum)

    Maximum packet length to receive

  • timeout (Numeric) (defaults to: @read_timeout)

    Number of seconds to wait for recvfrom operation to complete

Returns:

  • (String)

    Received data



132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/socketry/udp/socket.rb', line 132

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(timeout)
  end

  result
end

#recvfrom_nonblock(maxlen) ⇒ Socketry::UDP::Datagram, :wait_readable

Perform a non-blocking receive

Parameters:

  • maxlen (Fixnum)

    Maximum packet length to receive

Returns:



117
118
119
120
121
122
123
124
# File 'lib/socketry/udp/socket.rb', line 117

def 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

#send(msg, host: nil, port: nil) ⇒ Fixum

Send a UDP packet to a remote host

Parameters:

  • msg (String)

    Data to write to the remote host/port

  • host (String) (defaults to: nil)

    Remote host to send data to. May be omitted if ‘connect` was called previously

  • port (Fixnum) (defaults to: nil)

    UDP port to send data to. May be omitted if ‘connect` was called previously

Returns:

  • (Fixum)

    Number of bytes sent



154
155
156
157
158
159
160
161
162
163
164
# File 'lib/socketry/udp/socket.rb', line 154

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