In article <1173231146.477057.42960 / q40g2000cwq.googlegroups.com>, "theosib / gmail.com" <theosib / gmail.com> writes:
> I've done quite a bit of reading about socket programming in Ruby, but
> most of the examples show simple cases of accepting a socket
> connection, spitting out some fixed bit of data, and then closing.
> What about socket connections where you can't predict the length or
> timing of data that you'll receive?  None of the available web pages
> give any hint of how to work interactively with sockets.

To do the things that you seem to want to do, you will need to
program to the low-level "Berkeley" socket interface.  This is the
interface adopted by both the UNIX/POSIX/Linux and Microsoft WINSOCK
specifications, with slight alterations, of course.  Ruby exposes these
through the 'socket' library.

> Here are a few things that I was hoping maybe some knowledgable people
> could help me figure out how to do with sockets in Ruby:
> 
> - Block until 1 or more bytes have been received, and then return as
> many as have arrived (even if fewer than some buffer size)

This is the default behavior of the socket API.

> - Block until a fixed number of bytes have been received, or a time-
> out has elapsed

To wait for a "full" buffer, you can use the MSG_WAITALL flag to
recv* to wait for a full buffer.  A timeout would be effected by
using SO_RCVTIMEO at the underlying API, but that doesn't work
under Ruby due to a deficiency in the user-mode threads implementation.

In any case, the underlying APIs do not let you mix the two
concepts.  If a timeout were to occur, a partial buffer would be
returned if *any* data were available.  There is no way to eliminate
the possibility of a partial read while still enabling a timeout.

> - Check how many bytes have been received without blocking

There is no portable way to do this, and I recommend strongly
against it.  However, if you must, most UNIX-inspired and WINSOCK
systems support the FIONBIO ioctl.  Most implementations try to
give you an estimate of the number of octets that are available,
but this estimate is often low (and on some systems is simply
zero/one).

> - Detect when the other end has closed the socket

The only way to to do this portably (or easily) is to read the
available data.  When you read zero bytes, you have read all the
available data, and the connection has been closed.  There is no
portable way to determine the reason (possible causes include at
least a remote close, a remote reset, and a force close initiated
by the local host).
 

> - Distinguish between a timeout and a closed socket

I'm presuming that you mean here a timeout on a read call and not
the local host closing the socket.  In that case, I don't know of
any way to do that straight-forwardly in Ruby in a single call,
so I'd recommend the use of select with a non-blocking socket.  I
attach an example below. You should carefully read the Ruby and
platform documentation on fcntl and select.

A few notes on the example below:

- It implements a client reader for RFC 868 (Time Protocol).  This
  was an experiment in using Ruby and I've only exercised this
  program on UNIX/POSIX/Linux systems. I have exercised analogous,
  but non-Ruby programs, under WINSOCK.

- The use of non-blocking sockets is not mandatory.  The program
  should generally work without setting this mode, but there then
  might be a few rare cases where the program pauses inexplicably
  for indeterminate periods of time.

----------------------------------------------------------------------------
#! /usr/bin/env ruby

# rb_getnettime3 - an alternate form that uses a select call to effect
# a timeout when polling unresponsive hosts.

require 'socket'
require 'fcntl'

hostname = (ARGV.length > 0) ? ARGV[0] : '127.0.0.1'

timeport = Socket.getservbyname('time', 'udp')  # port 37
RFC868_POSIX_ADJMENT = 2_208_988_800    # diff between RFC 868 and POSIX

tsecs = 5

rmttime = 0
begin
        rcvd = nil
        UDPSocket.open { |sock|
                origflags = sock.fcntl(Fcntl::F_GETFL, 0)
                sock.fcntl(Fcntl::F_SETFL, origflags | Fcntl::O_NONBLOCK)

                sock.send('', 0, hostname, timeport)

                IO.select([sock], [], [], tsecs) or raise 'receive time out'

                rcvd = sock.recvfrom(4, 0)      # read in one 'N'
        }
        raise 'Bad format in response' unless rcvd.length == 2

        data, rmthost = rcvd
        raise 'Invalid response from remote host' unless data.length == 4

        rmttime = data.unpack('N')[0]
rescue RuntimeError => msg
        abort "Call to time port failed:  #{msg}"
end

adjtime = rmttime - RFC868_POSIX_ADJMENT
puts "Remote time: #{Time.at(adjtime)}"

----------------------------------------------------------------------------

-- 
.   Douglas Wells             .  Connection Technologies      .
.   Internet:  -sp9804- -at - contek.com-                     .