Socket Options

To kick off this set of chapters on advanced techniques we’ll look at socket options. Socket options are a low-level way to configure system-specific behaviour on sockets. So low-level, in fact, that Ruby doesn’t provide a fancy wrapper around the system calls.

SO_TYPE

Let’s begin by having a look at retrieving a socket option: the socket type.

require 'socket'

socket = TCPSocket.new('google.com', 80)
# Get an instance of Socket::Option representing the type of the socket.
opt = socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE)

# Compare the integer representation of the option to the integer 
# stored in Socket::SOCK_STREAM for comparison. 
opt.int == Socket::SOCK_STREAM #=> true
opt.int == Socket::SOCK_DGRAM #=> false

A call to getsockopt returns an instance of Socket::Option. When working at this level everything resolves to integers. So SocketOption#int gets the underlying integer associated with the return value.

In this case I’m retrieving the socket type (remember specifying this back when creating our first socket?), so I compare the int value against the various Socket type constants and find that it’s a STREAM socket.

Remember that Ruby always offers memoized symbols in place of these constants. The above can also be written as:

require 'socket'

socket = TCPSocket.new('google.com', 80)
# Use the symbol names rather than constants.
opt = socket.getsockopt(:SOCKET, :TYPE)

SO_REUSE_ADDR

This is a common option that every server should set.

The SO_REUSE_ADDR option tells the kernel that it’s OK for another socket to bind to the same local address that the server is using if it’s currently in the TCP TIME_WAIT state.

TIME_WAIT state?

This can come about when you close a socket that has pending data in its buffers. Remember calling write only guarantees that your data has entered the buffer layers? When you close a socket its pending data is not discarded.

Behind the scenes, the kernel leaves the connection open long enough to send out that pending data. This means that it actually has to send the data, and then wait for acknowledgement of receipt from the receiving end in case some data needs retransmitting.

If you close a server with pending data and immediately try to bind another socket on the same address (such as if you reboot the server immediately) then an Errno::EADDRINUSE will be raised unless the pending data has been accounted for. Setting SO_REUSE_ADDR will circumvent this problem and allow you to bind to an address still in use by another socket in the TIME_WAIT state. &&&

Here’s how to switch it on:

require 'socket'

server = TCPServer.new('localhost', 4481)
server.setsockopt(:SOCKET, :REUSEADDR, true)

server.getsockopt(:SOCKET, :REUSEADDR) #=> true

Note that TCPServer.new, Socket.tcp_server_loop and friends enable this option by default.

For a complete listing of socket options available on your system look at setsockopt(2).

System Calls From This chapter

  • Socket#setsockopt -> setsockopt(2)
  • Socket#getsockopt -> getsockopt(2)