I'm going to be working on a fairly basic networking application shortly. This is low level socket work. (I cannot use a nicety like DRB sadly.)
I'm very familiar with this kind of stuff in Perl, but have done very little socket work in Ruby. I need something that can reliably work with around 50 users sending light activity. There is the possibility of a large write to them here and there and that cannot block the process.
I know a little from past questions here:
http://rubyurl.com/miq
However, I think I remember someone saying Ruby 1.8.4 is better with nonblocking sockets. So, can some networking guru take a stab at these questions:
1. I need to use threads *and* nonblocking sockets to be safe, right?
2. Can I use gets() and puts() now in Ruby 1.8.4 or do I still need to do low level reads and writes?
3. Do I need to rescue Errno::EWOULDBLOCK?
4. This isn't going to work on Windows, right? (That's okay in this case. Just making sure.)
I'm sorry I don't have definitive answers. I'm going to
need to find out for sure pretty soon, myself. But . . . .
From looking at the changes in the ruby sources between 1.8.2
and 1.8.4, it _looks_ as though nonblocking sockets are pretty
well supported now. (Even on Windows!)
It would appear nonblocking is handled properly internally
whether you use ruby threads or not. (I.e. from the perspective
of the underlying mechanisms, the main ruby thread is just as
much a thread as any other ruby thread.)
In 1.8.4, you can:
require 'io/nonblock'
This adds methods to the IO class, allowing:
some_socket.nonblock = true
In the past, I've done all my ruby socket programming using the lower level send() and recv() methods. However, again from
reading the source (io.c and ext/openssl/lib/openssl/buffering.rb)
it looks like IO#readpartial or IO#sysread (but *not* IO#read)
may be the new favored approach to dealing with nonblocking
descriptors in general.
(In Windows, I'm pretty sure only sockets have any chance of
working with nonblocking semantics. Whereas in Unix, I'd presume support for any file descriptor. So using IO#sysread
and IO#syswrite, now that these methods apparently are nonblocking-savvy, should be more general than using send() and recv().)
If you look at ext/openssl/lib/openssl/buffering.rb, you'll
see what looks like a fairly generic module for building a buffered read and readpartial, as well as write, on top of
sysread and syswrite. I note that the lowest level read and
write methods here both rescue Errno::EAGAIN, and retry. I'm
a little surprised by that - because it appears that EAGAIN
is already being handled along with EWOULDBLOCK in io.c
anyway. But I haven't really studied the code in io.c, just
perused it.
Hmm..........
My sense of it--at least, this is what I plan to try in my
first test--is:
1. require 'io/nonblock' and set 'nonblock = true' on all
my sockets.
2. use a separate ruby thread to handle I/O for each socket.
3. use IO#readpartial to read... (note that the comments in
io.c say that IO#readpartial can block regardless of the nonblocking flag... but I'm pretty sure this means
block the ruby thread, NOT the native thread...!)
4. use IO#syswrite to write... (I think, with 1.8.4 and a
nonblocking socket, ruby _appears_ to be handling
EWOULDBLOCK internally, so that we can do a large write
but not block the whole process.)
- But maybe we need to rescue Errno::EAGAIN for some
reason, as seen in openssl/buffering.rb
With luck, this will work on Unix and Windows (sockets only
on windows, any file descriptor on unix), and never block
the whole native thread ruby runs in.
If so, we've got a reasonable solution for TCP. UDP had its
own unique blocking issues (related to linux kernels breaking
POSIX semantics for select/recv for packets with bad UDP
checksums.... or something like that.... I dunno if this has
changed since 1.8.2 or not...)
Or, yeah, maybe that EventMachine library... <grin>
Regards,
Bill
···
From: "James Edward Gray II" <james@grayproductions.net>