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.
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)