PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/cool.io/socket.rb

http://github.com/tarcieri/cool.io
Ruby | 233 lines | 160 code | 45 blank | 28 comment | 8 complexity | c126fe1c89c569332e6c8ee8e1b2d7e2 MD5 | raw file
Possible License(s): BSD-3-Clause, BSD-2-Clause
  1. #--
  2. # Copyright (C)2007 Tony Arcieri
  3. # You can redistribute this under the terms of the Ruby license
  4. # See file LICENSE for details
  5. #++
  6. require 'socket'
  7. require 'resolv'
  8. module Coolio
  9. class Socket < IO
  10. def self.connect(socket, *args)
  11. new(socket, *args).instance_eval do
  12. @_connector = Connector.new(self, socket)
  13. self
  14. end
  15. end
  16. # Just initializes some instance variables to avoid
  17. # warnings and calls super().
  18. def initialize *args
  19. @_failed = nil
  20. @_connector = nil
  21. super
  22. end
  23. watcher_delegate :@_connector
  24. remove_method :attach
  25. def attach(evloop)
  26. raise RuntimeError, "connection failed" if @_failed
  27. if @_connector
  28. @_connector.attach(evloop)
  29. return self
  30. end
  31. super
  32. end
  33. # Called upon completion of a socket connection
  34. def on_connect; end
  35. event_callback :on_connect
  36. # Called if a socket connection failed to complete
  37. def on_connect_failed; end
  38. event_callback :on_connect_failed
  39. # Called if a hostname failed to resolve when connecting
  40. # Defaults to calling on_connect_failed
  41. alias_method :on_resolve_failed, :on_connect_failed
  42. #########
  43. protected
  44. #########
  45. class Connector < IOWatcher
  46. def initialize(coolio_socket, ruby_socket)
  47. @coolio_socket, @ruby_socket = coolio_socket, ruby_socket
  48. super(ruby_socket, :w)
  49. end
  50. def on_writable
  51. evl = evloop
  52. detach
  53. if connect_successful?
  54. @coolio_socket.instance_eval { @_connector = nil }
  55. @coolio_socket.attach(evl)
  56. @ruby_socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, [1].pack("l"))
  57. @ruby_socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true)
  58. @coolio_socket.__send__(:on_connect)
  59. else
  60. @coolio_socket.instance_eval { @_failed = true }
  61. @coolio_socket.__send__(:on_connect_failed)
  62. end
  63. end
  64. #######
  65. private
  66. #######
  67. def connect_successful?
  68. @ruby_socket.getsockopt(::Socket::SOL_SOCKET, ::Socket::SO_ERROR).unpack('i').first == 0
  69. rescue IOError
  70. false
  71. end
  72. end
  73. end
  74. class TCPSocket < Socket
  75. attr_reader :remote_host, :remote_addr, :remote_port, :address_family
  76. watcher_delegate :@_resolver
  77. # Similar to .new, but used in cases where the resulting object is in a
  78. # "half-open" state. This is primarily used for when asynchronous
  79. # DNS resolution is taking place. We don't actually have a handle to
  80. # the socket we want to use to create the watcher yet, since we don't
  81. # know the IP address to connect to.
  82. def self.precreate(*args, &block)
  83. obj = allocate
  84. obj.__send__(:preinitialize, *args, &block)
  85. obj
  86. end
  87. # Perform a non-blocking connect to the given host and port
  88. # see examples/echo_client.rb
  89. # addr is a string, can be an IP address or a hostname.
  90. def self.connect(addr, port, *args)
  91. family = nil
  92. if (Resolv::IPv4.create(addr) rescue nil)
  93. family = ::Socket::AF_INET
  94. elsif(Resolv::IPv6.create(addr) rescue nil)
  95. family = ::Socket::AF_INET6
  96. end
  97. if family
  98. return super(TCPConnectSocket.new(family, addr, port), *args) # this creates a 'real' write buffer so we're ok there with regards to already having a write buffer from the get go
  99. end
  100. if host = Coolio::DNSResolver.hosts(addr)
  101. return connect(host, port, *args) # calls this same function
  102. end
  103. precreate(addr, port, *args)
  104. end
  105. # Called by precreate during asyncronous DNS resolution
  106. def preinitialize(addr, port, *args)
  107. @_write_buffer = ::IO::Buffer.new # allow for writing BEFORE DNS has resolved
  108. @remote_host, @remote_addr, @remote_port = addr, addr, port
  109. @_resolver = TCPConnectResolver.new(self, addr, port, *args)
  110. end
  111. private :preinitialize
  112. PEERADDR_FAILED = ["?", 0, "name resolusion failed", "?"]
  113. def initialize(socket)
  114. unless socket.is_a?(::TCPSocket) or socket.is_a?(TCPConnectSocket)
  115. raise TypeError, "socket must be a TCPSocket"
  116. end
  117. super
  118. @address_family, @remote_port, @remote_host, @remote_addr = (socket.peeraddr rescue PEERADDR_FAILED)
  119. end
  120. def peeraddr
  121. [@address_family, @remote_port, @remote_host, @remote_addr]
  122. end
  123. #########
  124. protected
  125. #########
  126. class TCPConnectSocket < ::Socket
  127. def initialize(family, addr, port, host = addr)
  128. @host, @addr, @port = host, addr, port
  129. @address_family = nil
  130. super(family, ::Socket::SOCK_STREAM, 0)
  131. begin
  132. connect_nonblock(::Socket.sockaddr_in(port, addr))
  133. rescue Errno::EINPROGRESS
  134. end
  135. end
  136. def peeraddr
  137. [
  138. @address_family == ::Socket::AF_INET ? 'AF_INET' : 'AF_INET6',
  139. @port,
  140. @host,
  141. @addr
  142. ]
  143. end
  144. end
  145. class TCPConnectResolver < Coolio::DNSResolver
  146. def initialize(socket, host, port, *args)
  147. @sock, @host, @port, @args = socket, host, port, args
  148. super(host)
  149. end
  150. def on_success(addr)
  151. host, port, args = @host, @port, @args
  152. @sock.instance_eval do
  153. # DNSResolver only supports IPv4 so we can safely assume IPv4 address
  154. begin
  155. socket = TCPConnectSocket.new(::Socket::AF_INET, addr, port, host)
  156. rescue Errno::ENETUNREACH
  157. on_connect_failed
  158. return
  159. end
  160. initialize(socket, *args)
  161. @_connector = Socket::Connector.new(self, socket)
  162. @_resolver = nil
  163. end
  164. @sock.attach(evloop)
  165. end
  166. def on_failure
  167. @sock.__send__(:on_resolve_failed)
  168. @sock.instance_eval do
  169. @_resolver = nil
  170. @_failed = true
  171. end
  172. return
  173. end
  174. end
  175. end
  176. class UNIXSocket < Socket
  177. attr_reader :path, :address_family
  178. # Connect to the given UNIX domain socket
  179. def self.connect(path, *args)
  180. new(::UNIXSocket.new(path), *args)
  181. end
  182. def initialize(socket)
  183. raise ArgumentError, "socket must be a UNIXSocket" unless socket.is_a? ::UNIXSocket
  184. super
  185. @address_family, @path = socket.peeraddr
  186. end
  187. end
  188. end