Module: Zerg::Support::SocketFactory

Defined in:
lib/zerg_support/socket_factory.rb

Class Method Summary collapse

Class Method Details

.addr_infos(options) ⇒ Object

Retrieves possible IP addresses to connect to based on the given options.

The retrieval is done via setsockopt.



159
160
161
162
# File 'lib/zerg_support/socket_factory.rb', line 159

def self.addr_infos(options)
  Socket.getaddrinfo connect_host(options), connect_port(options),
                     Socket::AF_INET, socket_type(options)
end

.bind(socket, options) ⇒ Object

Binds a socket to an address based on the options.



142
143
144
145
# File 'lib/zerg_support/socket_factory.rb', line 142

def self.bind(socket, options)
  socket.bind bind_socket_address(options)    
  socket
end

.bind_host(options) ⇒ Object

The host from a host:port IP address, in a form suitable for bind().



41
42
43
# File 'lib/zerg_support/socket_factory.rb', line 41

def self.bind_host(options)
  host_from_address(options[:in_addr]) or options[:in_host] or '0.0.0.0'
end

.bind_port(options) ⇒ Object

The port from a host:port IP address, in a form suitable for bind().



46
47
48
# File 'lib/zerg_support/socket_factory.rb', line 46

def self.bind_port(options)
  port_from_address(options[:in_addr]) or options[:in_port] or 0
end

.bind_socket_address(options) ⇒ Object

An address suitable for bind() based on the options.



51
52
53
# File 'lib/zerg_support/socket_factory.rb', line 51

def self.bind_socket_address(options)
  Socket::pack_sockaddr_in bind_port(options), bind_host(options)
end

.connect_host(options) ⇒ Object

The host from a host:port IP address, in a form suitable for connect().



56
57
58
# File 'lib/zerg_support/socket_factory.rb', line 56

def self.connect_host(options)
  options[:out_host] or host_from_address(options[:out_addr]) or 'localhost'
end

.connect_port(options) ⇒ Object

The port from a host:port IP address, in a form suitable for connect().



61
62
63
# File 'lib/zerg_support/socket_factory.rb', line 61

def self.connect_port(options)
  port_from_address(options[:out_addr]) or options[:out_port]
end

.connect_with_addr_info(socket, addr_info) ⇒ Object

Connects a socket to an address obtained from getaddrinfo().

Returns the socket for success, or nil if the connection failed.



172
173
174
175
176
177
178
179
# File 'lib/zerg_support/socket_factory.rb', line 172

def self.connect_with_addr_info(socket, addr_info)
  begin      
    socket.connect Socket.pack_sockaddr_in(addr_info[1], addr_info[3])
    socket
  rescue
    nil
  end    
end

.host_from_address(address) ⇒ Object

The host from a host:port IP address.

Empty string if the IP address does not contain a host (e.g. :3000)



24
25
26
# File 'lib/zerg_support/socket_factory.rb', line 24

def self.host_from_address(address)
  address and split_address(address)[0]
end

.new_inbound_socket(options) ⇒ Object

New inbound socket based on the options.



148
149
150
151
152
153
154
# File 'lib/zerg_support/socket_factory.rb', line 148

def self.new_inbound_socket(options)
  socket = Socket.new Socket::AF_INET, socket_type(options), Socket::PF_UNSPEC
  set_options socket, options
  set_options_on_accept_sockets socket, options
  sugar_socket_listen socket
  bind socket, options    
end

.new_outbound_socket(options) ⇒ Object

New outbound socket based on the options.



182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/zerg_support/socket_factory.rb', line 182

def self.new_outbound_socket(options)
  addr_infos = self.addr_infos options
  addr_infos.each do |addr_info|
    socket = new_outbound_socket_with_addr_info addr_info
    set_options socket, options      
    if connect_with_addr_info socket, addr_info
      sugar_socket_close socket
      return socket
    end
    socket.close rescue nil
  end
  nil
end

.new_outbound_socket_with_addr_info(addr_info) ⇒ Object

New outbound socket based on an address obtained from getaddrinfo().



165
166
167
# File 'lib/zerg_support/socket_factory.rb', line 165

def self.new_outbound_socket_with_addr_info(addr_info)
  Socket.new addr_info[4], addr_info[5], addr_info[6]
end

.outbound?(options) ⇒ Boolean

True for options requesting a connecting (as opposed to listening) socket.

Returns:

  • (Boolean)


36
37
38
# File 'lib/zerg_support/socket_factory.rb', line 36

def self.outbound?(options)
  [:out_port, :out_host, :out_addr].any? { |k| options[k] }
end

.port_from_address(address) ⇒ Object

The port from a host:port IP address.

nil if the IP address does not contain a port (e.g. localhost)



31
32
33
# File 'lib/zerg_support/socket_factory.rb', line 31

def self.port_from_address(address)
  address and (port_string = split_address(address)[1]) and port_string.to_i
end

.set_options(socket, options) ⇒ Object

Sets socket flags (via setsockopt) based on the options.



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
# File 'lib/zerg_support/socket_factory.rb', line 76

def self.set_options(socket, options)
  if options[:no_delay]
    if tcp?(options)
      socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true
    end
    socket.sync = true
  end
  
  if options[:reuse_addr]
    socket.setsockopt Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true
  end
  
  unless options[:reverse_lookup]
    if socket.respond_to? :do_not_reverse_lookup
      socket.do_not_reverse_lookup = true
    else
      # work around until the patch below actually gets committed:
      # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/2346
      BasicSocket.do_not_reverse_lookup = true
    end
  end
  
  if options[:linger]
    socket.setsockopt Socket::SOL_SOCKET, Socket::SO_LINGER,
                      [1, options[:linger]].pack('ii')
  else
    # No lingering sockets.
    socket.setsockopt Socket::SOL_SOCKET, Socket::SO_LINGER, [1, 0].pack('ii')
  end
end

.set_options_on_accept_sockets(socket, options) ⇒ Object

Hacks a socket’s accept method so that new sockets have the given flags set.

The flags are set in a similar manner to set_options.



110
111
112
113
114
115
116
117
118
119
# File 'lib/zerg_support/socket_factory.rb', line 110

def self.set_options_on_accept_sockets(socket, options)
  socket.instance_variable_set :@zerg_support_factory_options, options
  def socket.accept(*args)
    sock, addr = super
    Zerg::Support::SocketFactory.set_options sock,
                                             @zerg_support_factory_options
    Zerg::Support::SocketFactory.sugar_socket_close sock
    return sock, addr
  end
end

.socket(options) ⇒ Object

Kitchen-sink socket creation method. The following options are supported:

tcp:: forces the use of TCP (overrides the udp option)
udp:: selects UDP (the default is TCP)
out_port:: the port to connect an outgoing socket to
out_host:: the host to connect an outgoing socket to
out_addr:: the host:port address to connect an outgoing socket to; the
           host and port override out_port and out_host, which can be used
           in conjunction with out_addr to provide default values
in_port:: the port to bind a listening socket to
in_host:: the host to bind a listening socket to
in_addr:: the host:port address to bind a listening socket to; the host
           and port override in_port and in_host, which can be used in
           conjunction with in_addr to provide default values
no_delay:: disables Nagles' algorithm
reuse_addr:: allows binding other sockets to this socket's address
reverse_lookup:: enables reverse lookups on connections; this is the Ruby
                 default, but ZergSupport changes it for performance


213
214
215
216
217
218
219
# File 'lib/zerg_support/socket_factory.rb', line 213

def self.socket(options)
  if outbound? options
    new_outbound_socket options
  else      
    new_inbound_socket options
  end
end

.socket_type(options) ⇒ Object

The socket() type based on the options.



71
72
73
# File 'lib/zerg_support/socket_factory.rb', line 71

def self.socket_type(options)
  tcp?(options) ? Socket::SOCK_STREAM : Socket::SOCK_DGRAM
end

.split_address(address) ⇒ Object

Splits a host:port IP address into its components. IPv6 addresses welcome.

The port will be nil if the IP address doesn’t contain it.



10
11
12
13
14
15
16
17
18
19
# File 'lib/zerg_support/socket_factory.rb', line 10

def self.split_address(address)
  port_match = /(^|[^:])\:([^:].*)$/.match(address)
  if port_match
    port = port_match[2]
    host = address[0, address.length - port.length - 1]
    [(host.empty? ? nil : host), port]
  else
    [address, nil]
  end
end

.sugar_socket_close(socket) ⇒ Object

Sugar-coat the socket’s close() call with a better way to close a socket.



130
131
132
133
134
135
136
137
138
139
# File 'lib/zerg_support/socket_factory.rb', line 130

def self.sugar_socket_close(socket)
  def socket.close
    begin
      shutdown Socket::SHUT_WR
      loop { break if recv(65536).empty? }
    rescue SystemCallError
    end
    super
  end
end

.sugar_socket_listen(socket) ⇒ Object

Sugar-coat the socket’s listen() call with a default value for its argument.



122
123
124
125
126
127
# File 'lib/zerg_support/socket_factory.rb', line 122

def self.sugar_socket_listen(socket)
  def socket.listen(*args)
    args = [1000] if args.empty?
    super(*args)
  end
end

.tcp?(options) ⇒ Boolean

True if the options indicate TCP should be used, false for UDP.

Returns:

  • (Boolean)


66
67
68
# File 'lib/zerg_support/socket_factory.rb', line 66

def self.tcp?(options)
  options[:tcp] or !options[:udp]
end