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

/lib/celluloid/io/tcp_socket.rb

https://github.com/jwkoelewijn/celluloid-io
Ruby | 78 lines | 53 code | 11 blank | 14 comment | 3 complexity | 55028acfb22d178b9400c38eb53ba874 MD5 | raw file
  1. require 'socket'
  2. require 'resolv'
  3. module Celluloid
  4. module IO
  5. # TCPSocket with combined blocking and evented support
  6. class TCPSocket
  7. include CommonMethods
  8. extend Forwardable
  9. def_delegators :@socket, :read_nonblock, :write_nonblock, :close, :closed?
  10. def_delegators :@socket, :addr, :peeraddr, :setsockopt
  11. # Convert a Ruby TCPSocket into a Celluloid::IO::TCPSocket
  12. def self.from_ruby_socket(ruby_socket)
  13. # Some hax here, but whatever ;)
  14. socket = allocate
  15. socket.instance_variable_set(:@socket, ruby_socket)
  16. socket
  17. end
  18. # Opens a TCP connection to remote_host on remote_port. If local_host
  19. # and local_port are specified, then those parameters are used on the
  20. # local end to establish the connection.
  21. def initialize(remote_host, remote_port, local_host = nil, local_port = nil)
  22. # Is it an IPv4 address?
  23. begin
  24. @addr = Resolv::IPv4.create(remote_host)
  25. rescue ArgumentError
  26. end
  27. # Guess it's not IPv4! Is it IPv6?
  28. unless @addr
  29. begin
  30. @addr = Resolv::IPv6.create(remote_host)
  31. rescue ArgumentError
  32. end
  33. end
  34. # Guess it's not an IP address, so let's try DNS
  35. unless @addr
  36. # TODO: suppport asynchronous DNS
  37. # Even EventMachine doesn't do async DNS by default o_O
  38. addrs = Array(DNSResolver.new.resolve(remote_host))
  39. raise Resolv::ResolvError, "DNS result has no information for #{remote_host}" if addrs.empty?
  40. # Pseudorandom round-robin DNS support :/
  41. @addr = addrs[rand(addrs.size)]
  42. end
  43. case @addr
  44. when Resolv::IPv4
  45. family = Socket::AF_INET
  46. when Resolv::IPv6
  47. family = Socket::AF_INET6
  48. else raise ArgumentError, "unsupported address class: #{@addr.class}"
  49. end
  50. @socket = Socket.new(family, Socket::SOCK_STREAM, 0)
  51. @socket.bind Addrinfo.tcp(local_host, local_port) if local_host
  52. begin
  53. @socket.connect_nonblock Socket.sockaddr_in(remote_port, @addr.to_s)
  54. rescue Errno::EINPROGRESS
  55. wait_writable
  56. retry
  57. rescue Errno::EISCONN
  58. # We're now connected! Yay exceptions for flow control
  59. # NOTE: This is the approach the Ruby stdlib docs suggest ;_;
  60. end
  61. end
  62. def to_io
  63. @socket
  64. end
  65. end
  66. end
  67. end