A server socket listens for connections rather than initiating them. The typical lifecycle looks something like this:
We covered #1 already; now we’ll continue on with the rest of the list.
The second step in the lifecycle of a server socket is to bind to a port where it will listen for connections.
require 'socket' # First, create a new TCP socket. socket = Socket.new(:INET, :STREAM) # Create a C struct to hold the address for listening. addr = Socket.pack_sockaddr_in(4481, '0.0.0.0') # Bind to it. socket.bind(addr)
This is a low-level implementation that shows how to bind a TCP socket to a local port. In fact, it’s almost identical to the C code you would write to accomplish the same thing.
This particular socket is now bound to port
4481 on the local host. Other sockets will not be able to bind to this port; doing so would result in an
Errno::EADDRINUSE exception being raised. Client sockets will be able to connect to this socket using this port number, once a few more steps have been completed.
If you run that code block you’ll notice that it exits immediately. The code works but doesn’t yet do enough to actually listen for a connection. Keep reading to see how to put the server in
To recap, a server binds to a specific, agreed-upon port number which a client socket can then connect to.
Of course, Ruby provides syntactic sugar so that you never have to actually use
Socket#bind directly. But before learning the syntactic sugar it’s important that we see how to do things the hard way.
What port should I bind to?
This is an important consideration for anyone writing a server. Should you pick a random port number? How can you tell if some other program has already ‘claimed’ a port as their own?
In terms of what’s possible, any port from 1-65,535 can be used, but there are important conventions to consider before picking a port.
The first rule: don’t try to use a port in the 0-1024 range. These are considered ‘well-known’ ports and are reserved for system use. A few examples: HTTP traffic defaults to port 80, SMTP traffic defaults to port 25, rsync defaults to port 873. Binding to these ports typically requires root access.
The second rule: don’t use a port in the 49,000-65,535 range. These are the ephemeral ports. They’re typically used by services that don’t operate on a predefined port number but need ports for temporary purposes. They’re also an integral part of the connection negotiation process we’ll see in the next section. Picking a port in this range might cause issues for some of your users.
Besides that, any port from 1025-48,999 is fair game for your uses. If you’re planning on claiming one of those ports as the port for your server then you should have a look at the IANA list of registered ports and make sure that your choice doesn’t conflict with some other popular server out there.
What address should I bind to?
I bound to
0.0.0.0 in the above example, but what’s the difference when I bind to
22.214.171.124? The answer has to do with interfaces.
Earlier I mentioned that your system has a loopback interface represented with the IP address
127.0.0.1. It also has a physical, hardware-backed interface represented by a different IP address (let’s pretend it’s
192.168.0.5). When you
bind to a specific interface, represented by its IP address, your socket is only listening on that interface. It will ignore the others.
If you bind to
127.0.0.1 then your socket will only be listening on the loopback interface. In this case, only connections made to
127.0.0.1 will be routed to your server socket. Since this interface is only available locally, no external connections will be allowed.
If you bind to
192.168.0.5, in this example, then your socket will only be listening on that interface. Any clients that can address that interface will be listened for, but any connections made on
localhost will not be routed to that server socket.
If you want to listen on all interfaces then you can use
0.0.0.0. This will bind to any available interface, loopback or otherwise. Most of the time, this is what you want.
require 'socket' # This socket will bind to the loopback interface and will # only be listening for clients from localhost. local_socket = Socket.new(:INET, :STREAM) local_addr = Socket.pack_sockaddr_in(4481, '127.0.0.1') local_socket.bind(local_addr) # This socket will bind to any of the known interfaces and # will be listening for any client that can route messages # to it. any_socket = Socket.new(:INET, :STREAM) any_addr = Socket.pack_sockaddr_in(4481, '0.0.0.0') any_socket.bind(any_addr) # This socket attempts to bind to an unkown interface # and raises Errno::EADDRNOTAVAIL. error_socket = Socket.new(:INET, :STREAM) error_addr = Socket.pack_sockaddr_in(4481, '126.96.36.199') error_socket.bind(error_addr)
After creating a socket, and binding to a port, the socket needs to be told to listen for incoming connections.
require 'socket' # Create a socket and bind it to port 4481. socket = Socket.new(:INET, :STREAM) addr = Socket.pack_sockaddr_in(4481, '0.0.0.0') socket.bind(addr) # Tell it to listen for incoming connections. socket.listen(5)
The only addition to the code from the last chapter is a call to
listen on the socket.
If you run that code snippet it still exits immediately. There’s one more step in the lifecycle of a server socket required before it can process connections. That’s covered in the next chapter. First, more about
The Listen Queue
You may have noticed that we passed an integer argument to the
listen method. This number represents the maximum number of pending connections your server socket is willing to tolerate. This list of pending connections is called the listen queue.
Let’s say that your server is busy processing a client connection, when any new client connections arrive they’ll be put into the listen queue. If a new client connection arrives and the listen queue is full then the client will raise
How big should the listen queue be?
OK, so the size of the listen queue looks a bit like a magic number. Why wouldn’t we want to set that number to 10,000? Why would we ever want to refuse a connection? All good questions.
First, we should talk about limits. You can get the current maximum allowed listen queue size by inspecting
Socket::SOMAXCONN at runtime. On my Mac this number is 128. So I’m not able to use a number larger than that. The root user is able to increase this limit at the system level for servers that need it.
Let’s say you’re running a server and you’re getting reports of
Errno::ECONNREFUSED. Increasing the size of the listen queue would be a good starting point. But ultimately you don’t want to have connections waiting in your listen queue. That means that users of your service are having to wait for their responses. This may be an indication that you need more server instances or that you need a different architecture.
Generally you don’t want to be refusing connections. You can set the listen queue to the maximum allowed queue size using
Finally we get to the part of the lifecycle where the server is actually able to handle an incoming connection. It does this with the
accept method. Here’s how to create a listening socket and receive the first connection:
require 'socket' # Create the server socket. server = Socket.new(:INET, :STREAM) addr = Socket.pack_sockaddr_in(4481, '0.0.0.0') server.bind(addr) server.listen(128) # Accept a connection. connection, _ = server.accept
Now if you run that code you’ll notice that it doesn’t return immediately! That’s right, the
accept method will block until a connection arrives. Let’s give it one using netcat:
$ echo ohai | nc localhost 4481
When you run these snippets you should see the nc(1) program and the Ruby program exit successfully. It may not be the most epic finale ever, but it’s proof that everything is connected and working properly. Congrats!
Accept is blocking
accept call is a blocking call. It will block the current thread indefinitely until it receives a new connection.
Remember the listen queue we talked about in the last chapter?
accept simply pops the next pending connection off of that queue. If none are available it waits for one to be pushed onto it.
Accept returns an Array
In the example above I assigned two values from one call to
accept method actually returns an Array. The Array contains two elements: first, the connection, and second, an
Addrinfo object. This represents the remote address of the client connection.
Addrinfo is a Ruby class that represents a host and port number. It wraps up an endpoint representation nicely. You’ll see it as part of the standard
Socket interface in a few places.
You can construct one of these using something like
Addrinfo.tcp('localhost', 4481). Some useful methods are
#ip_port. Have a look at
$ ri Addrinfo for more.
Let’s begin by taking a closer look at the connection and address returned from
require 'socket' # Create the server socket. server = Socket.new(:INET, :STREAM) addr = Socket.pack_sockaddr_in(4481, '0.0.0.0') server.bind(addr) server.listen(128) # Accept a new connection. connection, _ = server.accept print 'Connection class: ' p connection.class print 'Server fileno: ' p server.fileno print 'Connection fileno: ' p connection.fileno print 'Local address: ' p connection.local_address print 'Remote address: ' p connection.remote_address
When the server gets a connection (using the netcat snippet from above) it outputs:
Connection class: Socket Server fileno: 5 Connection fileno: 8 Local address: #<Addrinfo: 127.0.0.1:4481 TCP> Remote address: #<Addrinfo: 127.0.0.1:58164 TCP>
The results from this little bit of code tell us a ton about how TCP connections are handled. Let’s dissect it a little bit at a time.
accept returns a ‘connection’, this code tells us that there’s no special connection class. A connection is actually an instance of
We know that
accept is returning an instance of
Socket, but this connection has a different file descriptor number (or
fileno) than the server socket. The file descriptor number is the kernel’s method for keeping track of open files in the current process.
Yep. At least in the land of Unix, everything is treated as a file. This includes files found on the filesystem as well as things like pipes, sockets, printers, etc.
This indicates that
accept has returned a brand new
Socket different from the server socket. This
Socket instance represents the connection. This is important. Each connection is represented by a new
Socket object so that the server socket can remain untouched and continue to accept new connections.
Our connection object knows about two addresses: the local address and the remote address. The remote address is the second return value returned from
accept but can also be accessed as
remote_address on the connection.
local_address of the connection refers to the endpoint on the local machine. The
remote_address of the connection refers to the endpoint at the other end, which might be on another host but, in our case, it’s on the same machine.
Each TCP connection is defined by this unique grouping of local-host, local-port, remote-host, and remote-port. The combination of these four properties must be unique for each TCP connection.
Let’s put that in perspective for a moment. You can initiate two simultaneous connections from the local host to a remote host, so long as the remote ports are unique. Similarly you can accept two simultaneous connections from a remote host to the same local port, provided that the remote ports are unique. But you cannot have two simultaneous connections to a remote host if the local ports and remote ports are identical.
The Accept Loop
accept returns one connection. In our code examples above the server accepts one connection and then exits. When writing a production server it’s almost certain that we’d want to continually listen for incoming connections so long as there are more available. This is easily accomplished with a loop:
require 'socket' # Create the server socket. server = Socket.new(:INET, :STREAM) addr = Socket.pack_sockaddr_in(4481, '0.0.0.0') server.bind(addr) server.listen(128) # Enter an endless loop of accepting and # handling connections. loop do connection, _ = server.accept # handle connection connection.close end
This is a common way to write certain kinds of servers using Ruby. It’s so common in fact that Ruby provides some syntactic sugar on top of it. We’ll look at Ruby wrapper methods at the end of this chapter.
Once a server has accepted a connection and finished processing it, the last thing for it to do is to
close that connection. This rounds out the create-process-close lifecycle of a connection.
Rather than paste another block of code, I’ll refer you to the one above. It calls
close on the connection before accepting a new one.
Closing on Exit
close needed? When your program exits, all open file descriptors (including sockets) will be closed for you. So why should you close them yourself? There are a few good reasons:
Resource usage. If you’re done with a socket, but you don’t close it, it’s possible to store references to sockets you’re no longer using. In Ruby’s case the garbage collector is your friend, cleaning up any unreferenced connections for you, but it’s a good idea to maintain full control over your resource usage and get rid of stuff you don’t need. Note that the garbage collector will close anything that it collects.
Open file limit. This is really an extension of the previous one. Every process is subject to a limit on the number of open files it can have. Remember that each connection is a file? Keeping around unneeded connections will continue to bring your process closer and closer to this open file limit, which may cause issues later.
To find out the allowed number of open files for the current process you can use
Process.getrlimit(:NOFILE). The returned value is an Array of the soft limit (user-configurable) and hard limit (system), respectively.
If you want to bump up your limit to the maximum then you can
Different Kinds of Closing
Given that sockets allow two-way communication (read/write) it’s actually possible to close just one of those channels.
require 'socket' # Create the server socket. server = Socket.new(:INET, :STREAM) addr = Socket.pack_sockaddr_in(4481, '0.0.0.0') server.bind(addr) server.listen(128) connection, _ = server.accept # After this the connection may no longer write data, but may still read data. connection.close_write # After this the connection may no longer read or write any data. connection.close_read
Closing the write stream will send an
EOF to the other end of the socket (more on EOF soon).
close_read methods make use of shutdown(2) under the hood. shutdown(2) is notably different than close(2) in that it causes a part of the connection to be fully shut down, even if there are copies of it lying around.
It’s possible to create copies of file descriptors using
Socket#dup. This will actually duplicate the underlying file descriptor at the operating system level using dup(2). But this is pretty uncommon, and you probably won’t see it.
The more common way that you can get a copy of a file descriptor is through
Process.fork. This method creates a brand new process (Unix only) that’s an exact copy of the current process. Besides providing a copy of everything in memory, any open file descriptors are dup(2)ed so that the new process gets a copy.
close will close the socket instance on which it’s called. If there are other copies of the socket in the system then those will not be closed and the underlying resources will not be reclaimed. Indeed, other copies of the connection may still exchange data even if one instance is closed.
close, will fully shut down communication on the current socket and other copies of it, thereby disabling any communication happening on the current instance as well as any copies. But it does not reclaim resources used by the socket. Each individual socket instance must still be
closed to complete the lifecycle.
require 'socket' # Create the server socket. server = Socket.new(:INET, :STREAM) addr = Socket.pack_sockaddr_in(4481, '0.0.0.0') server.bind(addr) server.listen(128) connection, _ = server.accept # Create a copy of the connection. copy = connection.dup # This shuts down communication on all copies of the connection. connection.shutdown # This closes the original connection. The copy will be closed # when the GC collects it. connection.close
We all know and love the elegant syntax that Ruby offers, and its extensions for creating and working with server sockets are no exception. These convenience methods wrap up the boilerplate code in custom classes and leverage Ruby blocks where possible. Let’s have a look.
First up is the
TCPServer class. It’s a clean way to abstract the ‘server construction’ part of the process.
require 'socket' server = TCPServer.new(4481)
Ah, now that feels more like Ruby code. That code is effectively replacing this:
require 'socket' server = Socket.new(:INET, :STREAM) addr = Socket.pack_sockaddr_in(4481, '0.0.0.0') server.bind(addr) server.listen(5)
I know which one I prefer to use!
TCPServer instance actually returns an instance of
Socket. The interface exposed by each of them is nearly identical, but with some key differences. The most notable of which is that
TCPServer#accept returns only the connection, not the
Notice that we didn’t specify the size of the listen queue for these constructors? Rather than using
Socket::SOMAXCONN, Ruby defaults to a listen queue of size
5. If you need a bigger listen queue you can call
TCPServer#listen after the fact.
As IPv6 gains momentum, your servers may need to be able to handle both IPv4 and IPv6. Using this Ruby wrapper will return two TCP sockets, one that can be reached via IPv4 and one that can be reached via IPv6, both listening on the same port.
require 'socket' servers = Socket.tcp_server_sockets(4481)
Besides constructing servers, Ruby also provides nice abstractions for handling connections.
loop to handle multiple connections? Using
loop is for chumps. Do it like this:
require 'socket' # Create the listener socket. server = TCPServer.new(4481) # Enter an endless loop of accepting and # handling connections. Socket.accept_loop(server) do |connection| # handle connection connection.close end
Note that connections are not automatically closed at the end of each block. The arguments that get passed into the block are the exact same ones that are returned from a call to
Socket.accept_loop has the added benefit that you can actually pass multiple listening sockets to it and it will accept connections on any of the passed-in sockets. This goes really well with
require 'socket' # Create the listener socket. servers = Socket.tcp_server_sockets(4481) # Enter an endless loop of accepting and # handling connections. Socket.accept_loop(servers) do |connection| # handle connection connection.close end
Notice that we’re passing a collection of sockets to
Socket.accept_loop and it handles them gracefully.
Wrapping it all into one
The granddaddy of the Ruby wrappers is
Socket.tcp_server_loop, it wraps all of the previous steps into one:
require 'socket' Socket.tcp_server_loop(4481) do |connection| # handle connection connection.close end
This method is really just a wrapper around
Socket.accept_loop, but you can’t write it any more succinctly than that!
System Calls From This Chapter