Class: Ionian::Socket

Inherits:
Object
  • Object
show all
Defined in:
lib/ionian/socket.rb

Overview

A convenient wrapper for TCP, UDP, and Unix client sockets.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(existing_socket = nil, **kwargs) {|socket| ... } ⇒ Socket

Creates a new socket or wraps an existing socket.

Parameters:

  • existing_socket (Socket) (defaults to: nil)

    An instantiated socket to be wrapped in and returned as an Ionian::Socket (for example, TCPSocket). A new socket will be created if this parameter is nil.

  • kwargs (Hash)

    :host is mandatory.

Options Hash (**kwargs):

  • :host (String)

    IP or hostname to connect to. Can contain the port in the format “host:port”.

  • :port (Fixnum) — default: 23

    Connection’s port number. Unused by the :unix protocol.

  • :protocol (:tcp, :udp, :unix) — default: :tcp

    Type of socket to create. :udp will be automatically selected for addresses in the multicast range, or if the broadcast flag is set.

  • :connect_timeout (Fixnum) — default: nil

    Number of seconds to wait when connecting before timing out. Raises Errno::EHOSTUNREACH.

  • :persistent (Boolean) — default: true

    The socket remains open after data is sent if this is true. The socket closes after data is sent and a packet is received if this is false.

  • :bind_port (Boolean) — default: :port

    Local UDP port to bind to for receiving data, if different than the remote port being connected to.

  • :broadcast (Boolean) — default: false

    Enable the SO_BROADCAST flag. Sets protocol to :udp implicitly.

  • :reuse_addr (Boolean) — default: false

    Enable the SO_REUSEADDR flag. Allows local address reuse.

  • :no_delay (Boolean) — default: false

    Enable the TCP_NODELAY flag. Disables Nagle algorithm.

  • :cork (Boolean) — default: false

    Enable the TCP_CORK flag. Buffers multiple writes into one segment.

  • :linger (Boolean) — default: false

    Enable the SO_LINGER flag. When #close is called, waits for the send buffer to empty before closing the socket.

  • :expression (Regexp, String)

    Overrides the Extension::IO#read_match regular expression for received data.

Yield Parameters:

  • socket (Ionian::Socket)

    This socket is yielded to the block. Socket flushes and closes when exiting the block.



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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/ionian/socket.rb', line 88

def initialize existing_socket = nil, **kwargs, &block
  @socket = existing_socket
  
  @ionian_match_handlers = []
  @ionian_error_handlers = []
  
  @expression = kwargs.fetch :expression, nil
  
  if existing_socket
    # Convert existing socket.
    @socket.extend Ionian::Extension::IO
    @socket.extend Ionian::Extension::Socket
    
    if existing_socket.is_a? UNIXSocket
      @host = existing_socket.path
      @port = nil
    else
      @host = existing_socket.remote_address.ip_address if existing_socket
      @port = existing_socket.remote_address.ip_port if existing_socket
    end
  
    if @socket.is_a? TCPSocket
      @protocol = :tcp
    elsif @socket.is_a? UDPSocket
      @protocol = :udp
    elsif @socket.is_a? UNIXSocket
      @protocol = :unix
    end
    
    @persistent = true # Existing sockets are always persistent.
    
    @socket.expression = @expression if @expression
    
    initialize_socket_methods
    
  else
    # Initialize new socket.
    
    # Parse host out of "host:port" if specified.
    host_port_array = kwargs.fetch(:host).to_s.split ':'
    
    @host           = host_port_array[0]
    @port           = kwargs.fetch :port, (host_port_array[1] || 23).to_i
    @bind_port      = kwargs.fetch :bind_port, @port
    @connect_timeout = kwargs.fetch :connect_timeout, nil
    
    @broadcast      = kwargs.fetch :broadcast, false
    
    # Automatically select UDP for the multicast range. Otherwise default to TCP.
    default_protocol = :tcp
    default_protocol = :udp  if Ionian::Extension::Socket.multicast? @host
    default_protocol = :unix if @host.start_with? '/'
    default_protocol = :udp  if @broadcast
    
    @protocol       = kwargs.fetch :protocol,   default_protocol
    @persistent     = kwargs.fetch :persistent, true
    @persistent     = true if @protocol == :udp
    
    @reuse_addr     = kwargs.fetch :reuse_addr, false
    @cork           = kwargs.fetch :cork,       false
    @no_delay       = kwargs.fetch :no_delay,   @persistent ? false : true
    
    # Default to false for persistent sockets, true for
    # nonpersistent sockets. When nonpersistent, the socket
    # should remain open to send data in the buffer after
    # close is called (typically right after write).
    # @linger         = kwargs.fetch :linger,     @persistent ? false : true
    # TODO: For some reason linger = true is causing tests to fail.
    @linger         = kwargs.fetch :linger,     false
  
    
    create_socket if @persistent
  end
  
  if block
    block.call self
    unless self.closed?
      self.flush
      self.close
    end
  end
end

Instance Attribute Details

#bind_portObject (readonly)

Local port number.



16
17
18
# File 'lib/ionian/socket.rb', line 16

def bind_port
  @bind_port
end

#hostObject (readonly)

IP address or URL of server.



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

def host
  @host
end

#portObject (readonly)

Remote port number.



13
14
15
# File 'lib/ionian/socket.rb', line 13

def port
  @port
end

#protocolObject (readonly) Also known as: protocol?

Returns a symbol of the type of protocol this socket uses: :tcp, :udp, :unix



20
21
22
# File 'lib/ionian/socket.rb', line 20

def protocol
  @protocol
end

Class Method Details

.create_broadcast_socket(**kwargs) ⇒ Ionian::Socket

Returns a broadcast socket.

Parameters:

  • kwargs (Hash)

    a customizable set of options

Options Hash (**kwargs):

  • :port (Fixnum)

    Port to broadcast on.

  • :address (String) — default: '255.255.255.255'

    Address to broadcast on.

Returns:

See Also:



29
30
31
32
33
# File 'lib/ionian/socket.rb', line 29

def self.create_broadcast_socket **kwargs
  kwargs[:host] = kwargs.delete(:address) || '255.255.255.255'
  kwargs[:broadcast] = true
  new **kwargs
end

Instance Method Details

#closed?Boolean

Returns true if the socket is closed.

Returns:

  • (Boolean)


280
281
282
283
# File 'lib/ionian/socket.rb', line 280

def closed?
  return true unless @socket
  @socket.closed?
end

#cmd(data, **kwargs) {|match| ... } ⇒ Array<MatchData>

Send a command (data) to the socket.

Parameters:

Yield Parameters:

  • match (MatchData)

    Received match.

Returns:

  • (Array<MatchData>)

    An array of received matches.

See Also:



204
205
206
207
208
209
210
211
212
213
214
# File 'lib/ionian/socket.rb', line 204

def cmd data, **kwargs, &block
  create_socket unless @persistent
  
  write data
  @socket.flush
  
  matches = @socket.read_match(kwargs) { |match| yield match if block_given? }
  @socket.close unless @persistent
  
  matches
end

#expressionRegexp

Returns the regular expression used to match incoming data.

Returns:

  • (Regexp)

    the regular expression used to match incoming data.



178
179
180
# File 'lib/ionian/socket.rb', line 178

def expression
  @expression || @socket.expression
end

#expression=(exp) ⇒ Object

Set the regular expression used to match incoming data.

Parameters:

  • exp (Regexp, String)

    Match expression.

See Also:



185
186
187
188
# File 'lib/ionian/socket.rb', line 185

def expression= exp
  @expression = exp
  @socket.expression = exp if @socket
end

#fdIO

Returns the file descriptor for this socket. For use with methods like IO.select.

Returns:

  • (IO)

    the file descriptor for this socket. For use with methods like IO.select.



173
174
175
# File 'lib/ionian/socket.rb', line 173

def fd
  @socket
end

#flushObject

Flushes buffered data to the operating system. This method has no effect on non-persistent sockets.



287
288
289
# File 'lib/ionian/socket.rb', line 287

def flush
  @socket.flush if @persistent
end

#has_data?(**kwargs) ⇒ Boolean

data until giving up. Set to nil for blocking.

Parameters:

  • kwargs (Hash)

    a customizable set of options

Options Hash (**kwargs):

  • :timeout (Fixnum, nil) — default: 0

    Number of seconds to wait for

Returns:

  • (Boolean)

    True if there is data in the receive buffer.



274
275
276
277
# File 'lib/ionian/socket.rb', line 274

def has_data? **kwargs
  return false unless @socket
  @socket.has_data? kwargs
end

#persistent?Boolean

Returns True if the socket remains open after writing data.

Returns:

  • (Boolean)

    True if the socket remains open after writing data.



191
192
193
# File 'lib/ionian/socket.rb', line 191

def persistent?
  @persistent == false || @persistent == nil ? false : true
end

#puts(*string) ⇒ Object

Writes the given string(s) to the socket and appends a newline character to any string not already ending with one.



293
294
295
# File 'lib/ionian/socket.rb', line 293

def puts *string
  self.write string.map{ |s| s.chomp }.join("\n") + "\n"
end

#register_error_handler {|Exception, self| ... } ⇒ Block Also known as: on_error

Register a block to be called when Extension::IO#run_match raises an error. Method callbacks can be registered with &object.method(:method).

Yields:

  • (Exception, self)

Returns:

  • (Block)

    a reference to the given block.



253
254
255
256
257
# File 'lib/ionian/socket.rb', line 253

def register_error_handler &block
  @ionian_error_handlers << block unless @ionian_error_handlers.include? block
  @socket.register_error_handler &block if @socket
  block
end

#register_match_handler {|MatchData, self| ... } ⇒ Block Also known as: on_match

Register a block to be called when Extension::IO#run_match receives matched data. Method callbacks can be registered with &object.method(:method).

Yields:

  • (MatchData, self)

Returns:

  • (Block)

    The given block.



221
222
223
224
225
# File 'lib/ionian/socket.rb', line 221

def register_match_handler &block
  @ionian_match_handlers << block unless @ionian_match_handlers.include? block
  @socket.register_match_handler &block if @socket
  block
end

#register_observer(&block) ⇒ Object

Deprecated.


230
231
232
233
# File 'lib/ionian/socket.rb', line 230

def register_observer &block
  STDOUT.puts "WARN: Call to deprecated method #{__method__}"
  register_match_handler &block
end

#unregister_error_handler(&block) ⇒ Object

Unregister a block from being called when a IO#run_match error is raised.



263
264
265
266
267
# File 'lib/ionian/socket.rb', line 263

def unregister_error_handler &block
  @ionian_error_handlers.delete_if { |o| o == block }
  @socket.unregister_error_handler &block if @socket
  block
end

#unregister_match_handler(&block) ⇒ Object

Unregister a block from being called when matched data is received.



236
237
238
239
240
# File 'lib/ionian/socket.rb', line 236

def unregister_match_handler &block
  @ionian_match_handlers.delete_if { |o| o == block }
  @socket.unregister_match_handler &block if @socket
  block
end

#unregister_observer(&block) ⇒ Object

Deprecated.


243
244
245
246
# File 'lib/ionian/socket.rb', line 243

def unregister_observer &block
  STDOUT.puts "WARN: Call to deprecated method #{__method__}"
  unregister_match_handler &block
end

#write(string) ⇒ Fixnum Also known as: <<

Writes the given string to the socket.

Returns:

  • (Fixnum)

    Number of bytes written.



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/ionian/socket.rb', line 299

def write string
  create_socket unless @persistent
  
  num_bytes = 0
  
  if @protocol == :udp
    num_bytes = @socket.send string, 0
  else
    num_bytes = @socket.write string
  end
  
  unless @persistent
    @socket.flush
    
    # Read in data to prevent RST packets.
    # TODO: Shutdown read stream instead?
    @socket.read_all nonblocking: true
    
    # TODO: Sleep added so that data can be read on the receiving
    # end. Can this be changed to shutdown write?
    # Why isn't so_linger taking care of this?
    # sleep 0.01
    @socket.close
  end
  
  num_bytes
end