#===============================================================================
# ** Socket - Creates and manages sockets.
#-------------------------------------------------------------------------------
# Author Ruby
# Version 1.8.1
#===============================================================================
class Socket
#--------------------------------------------------------------------------
# ● Constants
#--------------------------------------------------------------------------
AF_UNSPEC = 0
AF_UNIX = 1
AF_INET = 2
AF_IPX = 6
AF_APPLETALK = 16
PF_UNSPEC = 0
PF_UNIX = 1
PF_INET = 2
PF_IPX = 6
PF_APPLETALK = 16
SOCK_STREAM = 1
SOCK_DGRAM = 2
SOCK_RAW = 3
SOCK_RDM = 4
SOCK_SEQPACKET = 5
IPPROTO_IP = 0
IPPROTO_ICMP = 1
IPPROTO_IGMP = 2
IPPROTO_GGP = 3
IPPROTO_TCP = 6
IPPROTO_PUP = 12
IPPROTO_UDP = 17
IPPROTO_IDP = 22
IPPROTO_ND = 77
IPPROTO_RAW = 255
IPPROTO_MAX = 256
SOL_SOCKET = 65535
SO_DEBUG = 1
SO_REUSEADDR = 4
SO_KEEPALIVE = 8
SO_DONTROUTE = 16
SO_BROADCAST = 32
SO_LINGER = 128
SO_OOBINLINE = 256
SO_RCVLOWAT = 4100
SO_SNDTIMEO = 4101
SO_RCVTIMEO = 4102
SO_ERROR = 4103
SO_TYPE = 4104
SO_SNDBUF = 4097
SO_RCVBUF = 4098
SO_SNDLOWAT = 4099
TCP_NODELAY = 1
MSG_OOB = 1
MSG_PEEK = 2
MSG_DONTROUTE = 4
IP_OPTIONS = 1
IP_DEFAULT_MULTICAST_LOOP = 1
IP_DEFAULT_MULTICAST_TTL = 1
IP_MULTICAST_IF = 2
IP_MULTICAST_TTL = 3
IP_MULTICAST_LOOP = 4
IP_ADD_MEMBERSHIP = 5
IP_DROP_MEMBERSHIP = 6
IP_TTL = 7
IP_TOS = 8
IP_MAX_MEMBERSHIPS = 20
EAI_ADDRFAMILY = 1
EAI_AGAIN = 2
EAI_BADFLAGS = 3
EAI_FAIL = 4
EAI_FAMILY = 5
EAI_MEMORY = 6
EAI_NODATA = 7
EAI_NONAME = 8
EAI_SERVICE = 9
EAI_SOCKTYPE = 10
EAI_SYSTEM = 11
EAI_BADHINTS = 12
EAI_PROTOCOL = 13
EAI_MAX = 14
AI_PASSIVE = 1
AI_CANONNAME = 2
AI_NUMERICHOST = 4
AI_MASK = 7
AI_ALL = 256
AI_V4MAPPED_CFG = 512
AI_ADDRCONFIG = 1024
AI_DEFAULT = 1536
AI_V4MAPPED = 2048
#--------------------------------------------------------------------------
# ● Returns the associated IP address for the given hostname.
#--------------------------------------------------------------------------
def self.getaddress(host)
gethostbyname(host)[3].unpack("C4").join(".")
end
#--------------------------------------------------------------------------
# ● Returns the associated IP address for the given hostname.
#--------------------------------------------------------------------------
def self.getservice(serv)
case serv
when Numeric
return serv
when String
return getservbyname(serv)
else
raise "Please us an interger or string for services."
end
end
#--------------------------------------------------------------------------
# ● Returns information about the given hostname.
#--------------------------------------------------------------------------
def self.gethostbyname(name)
raise SocketError::ENOASSOCHOST if (ptr = Winsock.gethostbyname(name)) == 0
host = ptr.copymem(16).unpack("iissi")
[host[0].copymem(64).split("\0")[0], [], host[2], host[4].copymem(4).unpack("l")[0].copymem(4)]
end
#--------------------------------------------------------------------------
# ● Returns the user's hostname.
#--------------------------------------------------------------------------
def self.gethostname
buf = "\0" * 256
Winsock.gethostname(buf, 256)
buf.strip
end
#--------------------------------------------------------------------------
# ● Returns information about the given service.
#--------------------------------------------------------------------------
def self.getservbyname(name)
case name
when /echo/i
return 7
when /daytime/i
return 13
when /ftp/i
return 21
when /telnet/i
return 23
when /smtp/i
return 25
when /time/i
return 37
when /http/i
return 80
when /pop/i
return 110
else
raise "Service not recognized."
end
end
#--------------------------------------------------------------------------
# ● Creates an INET-sockaddr struct.
#--------------------------------------------------------------------------
def self.sockaddr_in(port, host)
begin
[AF_INET, getservice(port)].pack("sn") + gethostbyname(host)[3] + [].pack("x8")
rescue
nil
end
end
#--------------------------------------------------------------------------
# ● Creates a new socket and connects it to the given host and port.
#--------------------------------------------------------------------------
def self.open(*args)
socket = new(*args)
if block_given?
begin
yield socket
ensure
socket.close
end
end
nil
end
#--------------------------------------------------------------------------
# ● Creates a new socket.
#--------------------------------------------------------------------------
def initialize(domain, type, protocol)
SocketError.check if (@fd = Winsock.socket(domain, type, protocol)) == -1
@fd
end
#--------------------------------------------------------------------------
# ● Accepts incoming connections.
#--------------------------------------------------------------------------
def accept(flags = 0)
buf = "\0" * 16
SocketError.check if Winsock.accept(@fd, buf, flags) == -1
buf
end
#--------------------------------------------------------------------------
# ● Binds a socket to the given sockaddr.
#--------------------------------------------------------------------------
def self.bind(sockaddr)
SocketError.check if (ret = Winsock.bind(@fd, sockaddr, sockaddr.size)) == -1
ret
end
#--------------------------------------------------------------------------
# ● Closes a socket.
#--------------------------------------------------------------------------
def close
SocketError.check if (ret = Winsock.closesocket(@fd)) == -1
ret
end
#--------------------------------------------------------------------------
# ● Connects a socket to the given sockaddr.
#--------------------------------------------------------------------------
def connect(sockaddr)
ret = Winsock.connect(@fd, sockaddr, sockaddr.size)
SocketError.check if ret == -1
ret
end
#--------------------------------------------------------------------------
# ● Listens for incoming connections.
#--------------------------------------------------------------------------
def listen(backlog)
SocketError.check if (ret = Winsock.listen(@fd, backlog)) == -1
ret
end
#--------------------------------------------------------------------------
# ● Checks waiting data's status.
#--------------------------------------------------------------------------
def select(timeout)
SocketError.check if (ret = Winsock.select(1, [1, @fd].pack("ll"), 0, 0, [timeout, timeout * 1000000].pack("ll"))) == -1
ret
end
#--------------------------------------------------------------------------
# ● Checks if data is waiting.
#--------------------------------------------------------------------------
def ready?
not select(0) == 0
end
#--------------------------------------------------------------------------
# ● Reads data from socket.
#--------------------------------------------------------------------------
def read(len)
buf = "\0" * len
Win32API.new("msvcrt", "_read", "lpl", "l").call(@fd, buf, len)
buf
end
#--------------------------------------------------------------------------
# ● Returns recieved data.
#--------------------------------------------------------------------------
def recv(len, flags = 0)
buf = "\0" * len
SocketError.check if Winsock.recv(@fd, buf, buf.size, flags) == -1
buf
end
#--------------------------------------------------------------------------
# ● Sends data to a host.
#--------------------------------------------------------------------------
def send(data, flags = 0)
SocketError.check if (ret = Winsock.send(@fd, data, data.size, flags)) == -1
ret
end
#--------------------------------------------------------------------------
# * Gets
#--------------------------------------------------------------------------
def gets
# Create buffer
buffer = ""
# Loop Until "end of line"
while (char = recv(1)) != "\n"
buffer += char
end
# Return recieved data
return buffer
end
#--------------------------------------------------------------------------
# ● Writes data to socket.
#--------------------------------------------------------------------------
def write(data)
Win32API.new("msvcrt", "_write", "lpl", "l").call(@fd, data, 1)
end
end
#===============================================================================
# ** TCPSocket - Creates and manages TCP sockets.
#-------------------------------------------------------------------------------
# Author Ruby
# Version 1.8.1
#===============================================================================
class TCPSocket < Socket
#--------------------------------------------------------------------------
# ● Creates a new socket and connects it to the given host and port.
#--------------------------------------------------------------------------
def self.open(*args)
socket = new(*args)
if block_given?
begin
yield socket
ensure
socket.close
end
end
nil
end
#--------------------------------------------------------------------------
# ● Creates a new socket and connects it to the given host and port.
#--------------------------------------------------------------------------
def initialize(host, port)
super(AF_INET, SOCK_STREAM, IPPROTO_TCP)
connect([AF_INET, Socket.getservice(port), Socket.getaddress(host).split(".").collect { |b| b.to_i }].flatten.pack("snC4x8"))
end
end
#==============================================================================
# ■ SocketError
#------------------------------------------------------------------------------
# Default exception class for sockets.
#==============================================================================
class SocketError < StandardError
ENOASSOCHOST = "getaddrinfo: no address associated with hostname."
def self.check
errno = Winsock.WSAGetLastError
errno = Errno.constants.detect { |c| Errno.const_get(c).new.errno == errno }
if errno != nil
raise Errno.const_get(errno)
end
end
end
<span style="color:#000080; font-style:italic;">=begin
<span style="color:#000080; font-style:italic;">= monitor.rb
<span style="color:#000080; font-style:italic;">Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org>
<span style="color:#000080; font-style:italic;">This library is distributed under the terms of the Ruby license.
<span style="color:#000080; font-style:italic;">You can freely distribute/modify this library.
<span style="color:#000080; font-style:italic;">== example
<span style="color:#000080; font-style:italic;">This is a simple example.
<span style="color:#000080; font-style:italic;"> require 'monitor.rb'
<span style="color:#000080; font-style:italic;">
<span style="color:#000080; font-style:italic;"> buf = []
<span style="color:#000080; font-style:italic;"> buf.extend(MonitorMixin)
<span style="color:#000080; font-style:italic;"> empty_cond = buf.new_cond
<span style="color:#000080; font-style:italic;">
<span style="color:#000080; font-style:italic;"> # consumer
<span style="color:#000080; font-style:italic;"> Thread.start do
<span style="color:#000080; font-style:italic;"> loop do
<span style="color:#000080; font-style:italic;"> buf.synchronize do
<span style="color:#000080; font-style:italic;"> empty_cond.wait_while { buf.empty? }
<span style="color:#000080; font-style:italic;"> print buf.shift
<span style="color:#000080; font-style:italic;"> end
<span style="color:#000080; font-style:italic;"> end
<span style="color:#000080; font-style:italic;"> end
<span style="color:#000080; font-style:italic;">
<span style="color:#000080; font-style:italic;"> # producer
<span style="color:#000080; font-style:italic;"> while line = ARGF.gets
<span style="color:#000080; font-style:italic;"> buf.synchronize do
<span style="color:#000080; font-style:italic;"> buf.push(line)
<span style="color:#000080; font-style:italic;"> empty_cond.signal
<span style="color:#000080; font-style:italic;"> end
<span style="color:#000080; font-style:italic;"> end
<span style="color:#000080; font-style:italic;">The consumer thread waits for the producer thread to push a line
<span style="color:#000080; font-style:italic;">to buf while buf.empty?, and the producer thread (main thread)
<span style="color:#000080; font-style:italic;">reads a line from ARGF and push it to buf, then call
<span style="color:#000080; font-style:italic;">empty_cond.signal.
<span style="color:#000080; font-style:italic;">=end
#
# Adds monitor functionality to an arbitrary object by mixing the module with
# +include+. For example:
#
# require 'monitor.rb'
#
# buf = []
# buf.extend(MonitorMixin)
# empty_cond = buf.new_cond
#
# # consumer
# Thread.start do
# loop do
# buf.synchronize do
# empty_cond.wait_while { buf.empty? }
# print buf.shift
# end
# end
# end
#
# # producer
# while line = ARGF.gets
# buf.synchronize do
# buf.push(line)
# empty_cond.signal
# end
# end
#
# The consumer thread waits for the producer thread to push a line
# to buf while buf.empty?, and the producer thread (main thread)
# reads a line from ARGF and push it to buf, then call
# empty_cond.signal.
#
module MonitorMixin
#
# FIXME: This isn't documented in Nutshell.
#
# Since MonitorMixin.new_cond returns a ConditionVariable, and the example
# above calls while_wait and signal, this class should be documented.
#
class ConditionVariable
class Timeout < Exception; end
# Create a new timer with the argument timeout, and add the
# current thread to the list of waiters. Then the thread is
# stopped. It will be resumed when a corresponding #signal
# occurs.
def wait(timeout = nil)
@monitor.instance_eval {mon_check_owner()}
timer = create_timer(timeout)
Thread.critical = true
count = @monitor.instance_eval {mon_exit_for_cond()}
@waiters.push(Thread.current)
begin
Thread.stop
return true
rescue Timeout
return false
ensure
Thread.critical = true
begin
if timer && timer.alive?
Thread.kill(timer)
end
if @waiters.include?(Thread.current) # interrupted?
@waiters.delete(Thread.current)
end
@monitor.instance_eval {mon_enter_for_cond(count)}
ensure
Thread.critical = false
end
end
end
# call #wait while the supplied block returns +true+.
def wait_while
while yield
wait
end
end
# call #wait until the supplied block returns +true+.
def wait_until
until yield
wait
end
end
# Wake up and run the next waiter
def signal
@monitor.instance_eval {mon_check_owner()}
Thread.critical = true
t = @waiters.shift
t.wakeup if t
Thread.critical = false
Thread.pass
end
# Wake up all the waiters.
def broadcast
@monitor.instance_eval {mon_check_owner()}
Thread.critical = true
for t in @waiters
t.wakeup
end
@waiters.clear
Thread.critical = false
Thread.pass
end
def count_waiters
return @waiters.length
end
private
def initialize(monitor)
@monitor = monitor
@waiters = []
end
def create_timer(timeout)
if timeout
waiter = Thread.current
return Thread.start {
Thread.pass
sleep(timeout)
Thread.critical = true
waiter.raise(Timeout.new)
}
else
return nil
end
end
end
def self.extend_object(obj)
super(obj)
obj.instance_eval {mon_initialize()}
end
#
# Attempts to enter exclusive section. Returns +false+ if lock fails.
#
def mon_try_enter
result = false
Thread.critical = true
if @mon_owner.nil?
@mon_owner = Thread.current
end
if @mon_owner == Thread.current
@mon_count += 1
result = true
end
Thread.critical = false
return result
end
# For backward compatibility
alias try_mon_enter mon_try_enter
#
# Enters exclusive section.
#
def mon_enter
Thread.critical = true
mon_acquire(@mon_entering_queue)
@mon_count += 1
ensure
Thread.critical = false
end
#
# Leaves exclusive section.
#
def mon_exit
mon_check_owner
Thread.critical = true
@mon_count -= 1
if @mon_count == 0
mon_release
end
Thread.critical = false
Thread.pass
end
#
# Enters exclusive section and executes the block. Leaves the exclusive
# section automatically when the block exits. See example under
# +MonitorMixin+.
#
def mon_synchronize
mon_enter
begin
yield
ensure
mon_exit
end
end
alias synchronize mon_synchronize
#
# FIXME: This isn't documented in Nutshell.
#
# Create a new condition variable for this monitor.
# This facilitates control of the monitor with #signal and #wait.
#
def new_cond
return ConditionVariable.new(self)
end
private
def initialize(*args)
super
mon_initialize
end
# called by initialize method to set defaults for instance variables.
def mon_initialize
@mon_owner = nil
@mon_count = 0
@mon_entering_queue = []
@mon_waiting_queue = []
end
# Throw a ThreadError exception if the current thread
# does't own the monitor
def mon_check_owner
if @mon_owner != Thread.current
raise ThreadError, "current thread not owner"
end
end
def mon_acquire(queue)
while @mon_owner && @mon_owner != Thread.current
queue.push(Thread.current)
Thread.stop
Thread.critical = true
end
@mon_owner = Thread.current
end
def mon_release
@mon_owner = nil
t = @mon_waiting_queue.shift
t = @mon_entering_queue.shift unless t
t.wakeup if t
end
def mon_enter_for_cond(count)
mon_acquire(@mon_waiting_queue)
@mon_count = count
end
def mon_exit_for_cond
count = @mon_count
@mon_count = 0
return count
ensure
mon_release
end
end
# Monitors provide means of mutual exclusion for Thread programming.
# A critical region is created by means of the synchronize method,
# which takes a block.
# The condition variables (created with #new_cond) may be used
# to control the execution of a monitor with #signal and #wait.
#
# the Monitor class wraps MonitorMixin, and provides aliases
# alias try_enter try_mon_enter
# alias enter mon_enter
# alias exit mon_exit
# to access its methods more concisely.
class Monitor
include MonitorMixin
alias try_enter try_mon_enter
alias enter mon_enter
alias exit mon_exit
end
# Documentation comments:
# - All documentation comes from Nutshell.
# - MonitorMixin.new_cond appears in the example, but is not documented in
# Nutshell.
# - All the internals (internal modules Accessible and Initializable, class
# ConditionVariable) appear in RDoc. It might be good to hide them, by
# making them private, or marking them :nodoc:, etc.
# - The entire example from the RD section at the top is replicated in the RDoc
# comment for MonitorMixin. Does the RD section need to remain?
# - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but
# not synchronize.
# - mon_owner is in Nutshell, but appears as an accessor in a separate module
# here, so is hard/impossible to RDoc. Some other useful accessors
# (mon_count and some queue stuff) are also in this module, and don't appear
# directly in the RDoc output.
# - in short, it may be worth changing the code layout in this file to make the
# documentation easier
# Local variables:
# mode: Ruby
# tab-width: 8
# End: