Ruby Programming/Reference/Objects/Socket

Socket

edit

The Socket family of classes is the means that Ruby's standard library uses by default to communicate using networks.

The typical use flow is to create a socket

require 'socket'
a = TCPSocket.new 'some host', 80 # port 80

Then read to and write from a.

Now, if you try and read from a socket, it will typically block until some data comes in

incoming_string = a.recv(1024) # blocks until data becomes available, or the socket is closed.

You can avoid this blocking call by first checking if there is impending data, to do this you use the select call.

readable,writable,error = IO.select([a], [a], nil, 3) # blocks until the timeout occurs (3 seconds) or until a becomes readable.

At this point if a has any incoming data, readable will be an array like [a], otherwise it will be nil. if a is writable, then writable will be an array like [a]

error is typically never used, though it might be useful in certain instances.

The select method is really just a wrapper for the underlying select c call for whatever operating system you're on, though in general the semantics should be the same cross platform.

Another way to read without blocking is to call a.recv_nonblock(1024), which will raise an exception if nothing is available.

Example

edit

Here's an example:

 require 'socket'
 a = TCPSocket.new 'google.com', 80
 a.write "GET / HTTP/1.0\r\n\r\n"
 begin
   r,w,e = select([a], [w], nil) # r will contain those that are ready to be read from [if they read "" that means the socket was closed], 
                                 # w those ready to write to, e is hardly ever used--not actually sure when it is EVER used.
 rescue SystemCallError => e # this will rescue a multitude of network related errors, like Errno::ENETDOWN, etc.
 end

Echo Client/Server

edit

server.rb (run this first)

require 'socket'
a = TCPServer.new('', 3333) # '' means to bind to "all interfaces", same as nil or '0.0.0.0'
loop {
  connection = a.accept
  puts "received:" + connection.recv(1024)
  connection.write 'got something--closing now--here is your response message from the server'
  connection.close
}

client.rb

require 'socket'
a = TCPSocket.new('127.0.0.1', 3333) # could replace 127.0.0.1 with your "real" IP if desired.
a.write "hi server!"
puts "got back:" + a.recv(1024)
a.close

Output server:

C:\dev>ruby server.rb
received:hi server!

Output client:

C:\dev>ruby client.rb
got back:got something--closing now--here is your response message from the server

How to tell when a socket closes

edit

If BasicSocket#recv (available to all sockets) returns "" that means that the socket has been closed from the other side. The call to recv raising something like "ECONNRESET" means that the socket was closed, but ungracefully, from the other side.

Keeping a connection alive over time when there is no traffic being sent

edit

It's common knowledge that TCP sockets stay active until they're closed by one side or the other. But while they're open but not any transmitting data, it's possible that the connection becomes invalid. For example if the other side (the remote side) has gone away, or been rebooted or what not. It's also possible that intermediate NAT's will timed out your connection after awhile unused, thus also rendering the connection invalid. The real problem with this is that if you don't send any data, and the connection is invalid, you will only discover this fact *after* you send data, later. The notification can be thus delayed.

The fix to this problem is to either, every so often, send a ping message or your own, or to set the TCP_SOCKET keepalive option, like

socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)

Note that this option works for sockets that have already been opened. Also note from here that it appears to only send a ping every two hours or so.

If you have a timeout on read data (like you require them to send you something within x seconds or you will consider them dead), then you can track the last input using Time.now, then use the select command (with a reasonable timeout). Then, after each select, process the results, then iterate over your connections to see if old ones have become invalid because they haven't sent you data for a long time.

setting socket options

edit

Note also that many options must be set before a socket is opened.

here is some code demonstrating how to set one.

Flushing

edit

By default sockets are typically created with the NAGL optimization option turned on, which means that there is a tiny delay before sending data after a write, which is so that if more small data is sent before the packet leaves, it can combine them into one packet.

This can cause extra latency for the last of a set of packets. But can result in a small speedup by avoiding the overhead of multiple packets.

To get around the latency for the end packet, you'll either want to call #flush on your sockets immediately after writing (preferably leave NAGL on, write a lot, then call #flush), or set the socket option to disable NAGL. See Nagle's algorithm for more information.

UDP Socket Example

edit

server (run this first):

require 'socket'
BasicSocket.do_not_reverse_lookup = true
# Create socket and bind to address
client = UDPSocket.new
client.bind('0.0.0.0', 33333)
data, addr = client.recvfrom(1024) # if this number is too low it will drop the larger packets and never give them to you
puts "From addr: '%s', msg: '%s'" % [addr.join(','), data]
client.close

client:

require 'socket'
sock = UDPSocket.new
data = 'I sent this'
sock.send(data, 0, '127.0.0.1', 33333)
sock.close

UDP Broadcast Example

edit

See [1]

Multicast Example

edit

Linux

edit

See [2].

Windows

edit

See [3]

Also note "in Windows the setsockopt call has to come after the bind call." from http://www.ruby-forum.com/topic/133208

Alternatives

edit

Other ways, such as using the eventmachine gem, also provide Socketed programming, with some benefits like better functionality with many sockets (and some drawbacks). The Rev gem is similar, though written mostly in Ruby, not C.

Fibered

edit

You can use sockets with fibers (i.e. single threaded, but multiple connection) by using 1.9 + revactor or neverblock gem.


edit
  1. http://betterlogic.com/roger/?p=1646
  2. http://onestepback.org/index.cgi/Tech/Ruby/MulticastingInRuby.red
  3. http://www.ruby-forum.com/topic/200353