lib/ionian/socket.rb in ionian-0.3.0 vs lib/ionian/socket.rb in ionian-0.4.0

- old
+ new

@@ -1,38 +1,148 @@ -require 'ionian/io' -require 'socket' +require 'ionian/extension/socket' module Ionian - # A mixin for Socket objects. - # - # This module was designed to be extended by instantiated objects - # that implement the standard library Socket class. - # my_socket.extend Ionian::Socket - # - # Extending this module also extends Ionian::IO. - module Socket + class Socket - # Called automaticallly when the object is extended with #extend. - def self.extended(obj) - obj.extend Ionian::IO - obj.initialize_ionian_socket + ############ + # TODO NOTES + ############ + # Always lazily instiantiate @socket, even when persistent? + # May not work with forwarding method calls. + # Oh! Unless the forwarded methods check for @socket to exist. + # Will persistent methods have to check for the socket not to be + # closed as well? + + def initialize(**kvargs) + @socket = nil + + @host = kvargs.fetch :host + @port = kvargs.fetch :port, 23 + @expression = kvargs.fetch :expression, nil + @protocol = kvargs.fetch :protocol, :tcp + @persistent = kvargs.fetch :persistent, true + + create_socket if @persistent end + + # Returns a symbol of the type of protocol this socket uses: + # :tcp, :udp, :unix + def protocol? + @protocol + end - def initialize_ionian_socket + # Returns true if the socket remains open after writing data. + def persistent? + @persistent == false || @persistent == nil ? false : true end - # Returns true if the TCP_NODELAY flag is enabled (Nagle disabled). - # Otherwise false. - def no_delay - nagle_disabled = self.getsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY).data.ord - nagle_disabled > 0 ? true : false + # Send a command (data) to the socket. Returns received matches. + # Block yields received match. + # See Ionian::Extension::IO#read_match + def cmd(string, **kvargs, &block) + create_socket unless @persistent + @socket.write string + @socket.flush + + matches = @socket.read_match(kvargs) {|match| yield match if block_given?} + @socket.close unless @persistent + + matches end - # Setting to true enables the TCP_NODELAY flag (disables Nagle). - # Setting to false disables the flag (enables Nagle). - def no_delay=(value) - disable_nagle = value ? 1 : 0 - self.setsockopt ::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, disable_nagle + + ### Methods Forwarded To @socket ### + + # Returns true if there is data in the receive buffer. + # Args: + # Timeout: Number of seconds to wait for data until + # giving up. Set to nil for blocking. + def has_data?(**kvargs) + return false unless @socket + @socket.has_data? kvargs + end + + # Returns true if the socket is closed. + def closed? + return true unless @socket + @socket.closed? + end + + # Flushes buffered data to the operating system. + # This method has no effect on non-persistent sockets. + def flush + @socket.flush if @persistent + end + + # Writes the given string(s) to the socket and appends a + # newline character to any string not already ending with one. + def puts(*string) + self.write string.map{|s| s.chomp}.join("\n") + "\n" + end + + # Writes the given string to the socket. Returns the number of + # bytes written. + def write(string) + create_socket unless @persistent + num_bytes = @socket.write string + + unless @persistent + # Read in data to prevent RST packets. + has_data = ::IO.select [@socket], nil, nil, 0 + @socket.readpartial 0xFFFF if has_data + + @socket.close + end + + num_bytes + end + + alias_method :<<, :write + + + private + + def create_socket + @socket.close if @socket and not @socket.closed? + + case @protocol + when :tcp + @socket = ::TCPSocket.new @host, @port + when :udp + @socket = ::UDPSocket.new + @socket.connect @host, @port + when :unix + @socket = ::UNIXSocket.new @host + end + + @socket.extend Ionian::Extension::Socket + @socket.expression = @expression if @expression + + initialize_socket_methods + end + + # Expose the @socket methods that haven't been defined by this class. + # Only do this once for performance -- when non-persistent sockets are + # recreated, they should be of the same type of socket. + def initialize_socket_methods + # Only initialize once, lazily. + # For non-persistent sockets, this forwards the socket methods + # the first time data is sent -- when the new socket is created. + return if @socket_methods_initialized + + # Forward undefined methods to @socket. + # This was chosen over method_missing to avoid traversing the object + # hierarchy on every method call, like transmitting data. + @socket.methods + .select {|m| @socket.respond_to? m} + .select {|m| not self.respond_to? m} + .each do |m| + self.singleton_class.send :define_method, m do |*args, &block| + @socket.__send__ m, *args, &block + end + end + + @socket_methods_initialized = true end end end \ No newline at end of file