How to do INTERESTING things with Ruby sockets?


#1

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.

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)

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

  • Check how many bytes have been received without blocking

  • Detect when the other end has closed the socket

  • Distinguish between a timeout and a closed socket

Thank you for your help!


#2

On 3/6/07, removed_email_address@domain.invalid removed_email_address@domain.invalid wrote:

  • Distinguish between a timeout and a closed socket
    You don’t say if this is a learning exercise in low-level socket
    programming
    or if you actually have to develop an app. If it’s the latter, you may
    want
    to look at the Ruby/EventMachine library. It wraps up the low-level
    socket
    issues so you can concentrate on the app-level code.

#3

In article removed_email_address@domain.invalid,
“removed_email_address@domain.invalid” removed_email_address@domain.invalid 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)}”