[前][次][番号順一覧][スレッド一覧]

ruby-changes:10093

From: akr <ko1@a...>
Date: Sat, 17 Jan 2009 22:04:36 +0900 (JST)
Subject: [ruby-changes:10093] Ruby:r21636 (trunk): * ext/socket/lib/socket.rb: new file.

akr	2009-01-17 22:04:03 +0900 (Sat, 17 Jan 2009)

  New Revision: 21636

  http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=21636

  Log:
    * ext/socket/lib/socket.rb: new file.

  Added directories:
    trunk/ext/socket/lib/
  Added files:
    trunk/ext/socket/lib/socket.rb
  Modified files:
    trunk/ChangeLog
    trunk/NEWS
    trunk/ext/.document

Index: ChangeLog
===================================================================
--- ChangeLog	(revision 21635)
+++ ChangeLog	(revision 21636)
@@ -1,3 +1,7 @@
+Sat Jan 17 22:01:15 2009  Tanaka Akira  <akr@f...>
+
+	* ext/socket/lib/socket.rb: new file.
+
 Sat Jan 17 19:33:48 2009  Yuki Sonoda (Yugui)  <yugui@y...>
 
 	* configure.in (VCSUP): fixed the cases for git-svn or git.
Index: ext/.document
===================================================================
--- ext/.document	(revision 21635)
+++ ext/.document	(revision 21636)
@@ -21,6 +21,7 @@
 socket/unixserver.c
 socket/socket.c
 socket/constants.c
+socket/lib/socket.rb
 stringio/stringio.c
 strscan/strscan.c
 win32ole
Index: ext/socket/lib/socket.rb
===================================================================
--- ext/socket/lib/socket.rb	(revision 0)
+++ ext/socket/lib/socket.rb	(revision 21636)
@@ -0,0 +1,373 @@
+require 'socket.so'
+
+class AddrInfo
+  # creates an AddrInfo object from the arguments.
+  #
+  # The arguments are interpreted as similar to self.
+  #
+  #   AddrInfo.tcp("0.0.0.0", 4649).family_addrinfo("www.ruby-lang.org", 80)
+  #   #=> #<AddrInfo: 221.186.184.68:80 TCP (www.ruby-lang.org:80)>
+  #
+  #   AddrInfo.unix("/tmp/sock").family_addrinfo("/tmp/sock2")
+  #   #=> #<AddrInfo: /tmp/sock2 SOCK_STREAM>
+  #
+  def family_addrinfo(*args)
+    if args.empty?
+      raise ArgumentError, "no address specified"
+    elsif AddrInfo === args.first
+      raise ArgumentError, "too man argument" if args.length != 1
+    elsif self.ip?
+      raise ArgumentError, "IP address needs host and port but #{args.length} arguments given" if args.length != 2
+      host, port = args
+      AddrInfo.getaddrinfo(host, port, self.pfamily, self.socktype, self.protocol)[0]
+    elsif self.unix?
+      raise ArgumentError, "UNIX socket needs single path argument but #{args.length} arguments given" if args.length != 1
+      path, = args
+      AddrInfo.unix(path)
+    else
+      raise ArgumentError, "unexpected family"
+    end
+  end
+
+  def connect_internal(local_addrinfo)
+    sock = Socket.new(self.pfamily, self.socktype, self.protocol)
+    begin
+      sock.ipv6only! if self.ipv6?
+      sock.bind local_addrinfo if local_addrinfo
+      sock.connect(self)
+      if block_given?
+        yield sock
+      else
+        sock
+      end
+    ensure
+      sock.close if !sock.closed? && (block_given? || $!)
+    end
+  end
+  private :connect_internal
+
+  # creates a socket connected to self.
+  #
+  # If one or more arguments given as _local_addr_args_,
+  # it is used as the local address of the socket.
+  # _local_addr_args_ is given for family_addrinfo to obtain actual address.
+  #
+  # If no arguments given, the local address of the socket is not bound.
+  #
+  # If a block is given, it is called with the socket and the value of the block is returned.
+  # The socket is returned otherwise.
+  #
+  #   AddrInfo.tcp("www.ruby-lang.org", 80).connect_from("0.0.0.0", 4649) {|s|
+  #     s.print "GET / HTTP/1.0\r\n\r\n"
+  #     p s.read
+  #   }
+  #
+  #   # AddrInfo object can be taken for the argument.
+  #   AddrInfo.tcp("www.ruby-lang.org", 80).connect_from(AddrInfo.tcp("0.0.0.0", 4649)) {|s|
+  #     s.print "GET / HTTP/1.0\r\n\r\n"
+  #     p s.read
+  #   }
+  #
+  def connect_from(*local_addr_args, &block)
+    connect_internal(family_addrinfo(*local_addr_args), &block)
+  end
+
+  # creates a socket connected to self.
+  #
+  # If a block is given, it is called with the socket and the value of the block is returned.
+  # The socket is returned otherwise.
+  #
+  #   AddrInfo.tcp("www.ruby-lang.org", 80).connect {|s|
+  #     s.print "GET / HTTP/1.0\r\n\r\n"
+  #     p s.read
+  #   }
+  #
+  def connect(&block)
+    connect_internal(nil, &block)
+  end
+
+  # creates a socket connected to _remote_addr_args_ and bound to self.
+  #
+  # If a block is given, it is called with the socket and the value of the block is returned.
+  # The socket is returned otherwise.
+  #
+  #   AddrInfo.tcp("0.0.0.0", 4649).connect_to("www.ruby-lang.org", 80) {|s|
+  #     s.print "GET / HTTP/1.0\r\n\r\n"
+  #     p s.read
+  #   }
+  #
+  def connect_to(*remote_addr_args, &block)
+    remote_addrinfo = family_addrinfo(*remote_addr_args)
+    remote_addrinfo.send(:connect_internal, self, &block)
+  end
+
+  # creates a socket bound to self.
+  #
+  # If a block is given, it is called with the socket and the value of the block is returned.
+  # The socket is returned otherwise.
+  #
+  #   AddrInfo.udp("0.0.0.0", 9981).bind {|s|
+  #     s.local_address.connect {|s| s.send "hello", 0 }
+  #     p s.recv(10) #=> "hello"
+  #   }
+  #
+  def bind
+    sock = Socket.new(self.pfamily, self.socktype, self.protocol)
+    begin
+      sock.ipv6only! if self.ipv6?
+      sock.setsockopt(:SOCKET, :REUSEADDR, 1)
+      sock.bind(self)
+      if block_given?
+        yield sock
+      else
+        sock
+      end
+    ensure
+      sock.close if !sock.closed? && (block_given? || $!)
+    end
+  end
+
+  # creates a listening socket bound to self.
+  def listen(backlog=5)
+    sock = Socket.new(self.pfamily, self.socktype, self.protocol)
+    begin
+      sock.ipv6only! if self.ipv6?
+      sock.setsockopt(:SOCKET, :REUSEADDR, 1)
+      sock.bind(self)
+      sock.listen(backlog)
+      if block_given?
+        yield sock
+      else
+        sock
+      end
+    ensure
+      sock.close if !sock.closed? && (block_given? || $!)
+    end
+  end
+
+  # iterates over the list of AddrInfo objects obtained by AddrInfo.getaddrinfo.
+  #
+  #   AddrInfo.foreach(nil, 80) {|x| p x }
+  #   #=> #<AddrInfo: 127.0.0.1:80 TCP (:80)>
+  #   #   #<AddrInfo: 127.0.0.1:80 UDP (:80)>
+  #   #   #<AddrInfo: [::1]:80 TCP (:80)>
+  #   #   #<AddrInfo: [::1]:80 UDP (:80)>
+  #
+  def self.foreach(nodename, service, family=nil, socktype=nil, protocol=nil, flags=nil, &block)
+    AddrInfo.getaddrinfo(nodename, service, family, socktype, protocol, flags).each(&block)
+  end
+end
+
+class Socket
+  # enable the socket option IPV6_V6ONLY if IPV6_V6ONLY is available.
+  def ipv6only!
+    if Socket.const_defined?(:IPV6_V6ONLY)
+      self.setsockopt(:IPV6, :V6ONLY, 1)
+    end
+  end
+
+  # creates a new socket object connected to host:port using TCP.
+  #
+  # If local_host:local_port is given,
+  # the socket is bound to it.
+  #
+  # If a block is given, the block is called with the socket.
+  # The value of the block is returned.
+  # The socket is closed when this method returns.
+  #
+  # If no block is given, the socket is returned.
+  #
+  #   Socket.tcp("www.ruby-lang.org", 80) {|sock|
+  #     sock.print "GET / HTTP/1.0\r\n\r\n"
+  #     sock.close_write
+  #     print sock.read
+  #   }
+  #
+  def self.tcp(host, port, local_host=nil, local_port=nil) # :yield: socket
+    last_error = nil
+    ret = nil
+
+    local_addr_list = nil
+    if local_host != nil || local_port != nil
+      local_addr_list = AddrInfo.getaddrinfo(local_host, local_port, nil, :STREAM, nil)
+    end
+
+    AddrInfo.foreach(host, port, nil, :STREAM) {|ai|
+      if local_addr_list
+        local_addr = local_addr_list.find {|local_ai| local_ai.afamily == ai.afamily }
+        next if !local_addr
+      else
+        local_addr = nil
+      end
+      begin
+        sock = local_addr ? ai.connect_from(local_addr) : ai.connect
+      rescue SystemCallError
+        last_error = $!
+        next
+      end
+      ret = sock
+      break
+    }
+    if !ret
+      if last_error
+        raise last_error
+      else
+        raise SocketError, "no appropriate local address"
+      end
+    end
+    if block_given?
+      begin
+        yield ret
+      ensure
+        ret.close if !ret.closed?
+      end
+    else
+      ret
+    end
+  end
+
+  # creates a TCP server on _port_ and calls the block for each connection accepted.
+  # The block is called with a socket and a client_address as an AddrInfo object.
+  #
+  # If _host_ is specified, it is used with _port_ to determine the server addresses.
+  #
+  # The socket is *not* closed when the block returns.
+  # So application should close it explicitly.
+  #
+  # This method calls the block sequentially.
+  # It means that the next connection is not accepted until the block returns.
+  # So concurrent mechanism, thread for example, should be used to service multiple clients at a time.
+  #
+  # Note that AddrInfo.getaddrinfo is used to determine the server socket addresses.
+  # When AddrInfo.getaddrinfo returns two or more addresses,
+  # IPv4 and IPv6 address for example,
+  # all of them are used.
+  # Socket.tcp_server_loop succeeds if one socket can be used at least.
+  #
+  #   # Sequential echo server.
+  #   # It services only one client at a time.
+  #   Socket.tcp_server_loop(16807) {|sock, client_addrinfo|
+  #     begin
+  #       IO.copy_stream(sock, sock)
+  #     ensure
+  #       sock.close
+  #     end
+  #   }
+  #
+  #   # Threaded echo server
+  #   # It services multiple clients at a time.
+  #   # Note that it may accept connections too much.
+  #   Socket.tcp_server_loop(16807) {|sock, client_addrinfo|
+  #     Thread.new {
+  #       begin
+  #         IO.copy_stream(sock, sock)
+  #       ensure
+  #         sock.close
+  #       end
+  #     }
+  #   }
+  #
+  def self.tcp_server_loop(host=nil, port) # :yield: socket, client_addrinfo
+    last_error = nil
+    sockets = []
+    AddrInfo.foreach(host, port, nil, :STREAM, nil, Socket::AI_PASSIVE) {|ai|
+      begin
+        s = ai.listen
+      rescue SystemCallError
+        last_error = $!
+        next
+      end
+      sockets << s
+    }
+    if sockets.empty?
+      raise last_error
+    end
+    loop {
+      readable, _, _ = IO.select(sockets)
+      readable.each {|r|
+        begin
+          sock, addr = r.accept_nonblock
+        rescue Errno::EWOULDBLOCK
+          next
+        end
+        yield sock, addr
+      }
+    }
+  ensure
+    sockets.each {|s|
+      s.close if !s.closed?
+    }
+  end
+
+  # creates a new socket connected to path using UNIX socket socket.
+  #
+  # If a block is given, the block is called with the socket.
+  # The value of the block is returned.
+  # The socket is closed when this method returns.
+  #
+  # If no block is given, the socket is returned.
+  #
+  #   # talk to /tmp/sock socket.
+  #   Socket.unix("/tmp/sock") {|sock|
+  #     t = Thread.new { IO.copy_stream(sock, STDOUT) }
+  #     IO.copy_stream(STDIN, sock)
+  #     t.join
+  #   }
+  #
+  def self.unix(path) # :yield: socket
+    addr = AddrInfo.unix(path)
+    sock = addr.connect
+    if block_given?
+      begin
+        yield sock
+      ensure
+        sock.close if !sock.closed?
+      end
+    else
+      sock
+    end
+  end
+
+  # creates a UNIX socket server on _path_.
+  # It calls the block for each socket accepted.
+  #
+  # If _host_ is specified, it is used with _port_ to determine the server ports.
+  #
+  # The socket is *not* closed when the block returns.
+  # So application should close it.
+  #
+  # This method deletes the socket file pointed by _path_ at first if
+  # the file is a socket file and it is owned by the user of the application.
+  # This is safe only if the directory of _path_ is not changed by a malicious user.
+  # So don't use /tmp/malicious-users-directory/socket.
+  # Note that /tmp/socket and /tmp/your-private-directory/socket is safe assuming that /tmp has sticky bit.
+  #
+  #   # Sequential echo server.
+  #   # It services only one client at a time.
+  #   Socket.unix_server_loop("/tmp/sock") {|sock, client_addrinfo|
+  #     begin
+  #       IO.copy_stream(sock, sock)
+  #     ensure
+  #       sock.close
+  #     end
+  #   }
+  #
+  def self.unix_server_loop(path) # :yield: socket, client_addrinfo
+    begin
+      st = File.lstat(path)
+    rescue Errno::ENOENT
+    end
+    if st && st.socket? && st.owned?
+      File.unlink path
+    end
+    serv = AddrInfo.unix(path).listen
+    loop {
+      sock, addr = serv.accept
+      yield sock, addr
+    }
+  ensure
+    serv.close if serv && !serv.closed?
+  end
+
+end
+
Index: NEWS
===================================================================
--- NEWS	(revision 21635)
+++ NEWS	(revision 21636)
@@ -74,6 +74,7 @@
 * socket
   * Socket.pair and UNIXSocket.pair takes a block.
   * AddrInfo class
+  * Socket.{tcp,tcp_server_loop,unix,unix_server_loop}
   * Socket#{recvfrom,recvfrom_nonblock,accept,accept_nonblock,sysaccept}
     returns a sender address as AddrInfo object instead of a string.
   * BasicSocket#local_address

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]