From: see@... (Douglas Wells) Date: 2007-03-08T04:10:10+09:00 Subject: Re: How to do INTERESTING things with Ruby sockets??? In article <1173231146.477057.42960@q40g2000cwq.googlegroups.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- .