Your First Socket
Let’s hit the ground running with an example.
Ruby’s Socket Library
Ruby’s Socket classes are not loaded by default. Everything that you need can be imported with require 'socket'
. This includes a whole lot of different classes for TCP sockets, UDP sockets, as well as all the necessary primitives. You’ll get a look at some of these throughout the book.
The 'socket' library is part of Ruby's standard library. Similar to 'openssl', 'zlib', or 'curses', the 'socket' library provides thin bindings to dependable C libraries that have remained stable over many releases of Ruby.
So don’t forget to require 'socket'
before trying to create a socket.
Creating Your First Socket
On that note, let’s dive in and create a socket:
require 'socket'
socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
This creates a socket of type STREAM
in the INET
domain. The INET
is short for internet and specifically refers to a socket in the IPv4 family of protocols.
The STREAM
part says you’ll be communicating using a stream. This is provided by TCP. If you had said DGRAM
(for datagram) instead of STREAM
that would refer to a UDP socket. The type tells the kernel what kind of socket to create.
Understanding Endpoints
I just threw out some new language there in talking about IPv4. Let’s understand IPv4 and addressing before continuing.
When there are two sockets that want to communicate, they need to know where to find each other. This works much like a phone call: if you want to have a phone conversation with someone then you need to know their phone number.
Sockets use IP addresses to route messages to specific hosts. A host is identified by a unique IP address. This is its ‘phone number’.
Above I specifically mentioned IPv4 addresses. An IPv4 address typically looks something like this: 192.168.0.1
. It’s four numbers <= 255 joined with dots. What does that do? Armed with an IP address one host is able to route data to another host at that specific IP address.
It's easy enough to imagine socket communication when you know the address of the host you want to communicate with, but how does one get that address? Does it need to be memorized? Written down? Thankfully no.
You've likely heard of DNS before. This is a system that maps host names to IP addresses. In this way you don't need to remember the specific address of the host you want to talk to, but you do need to remember its name. Then you can ask DNS to resolve that name to an address. Even if the underlying address changes, the host name will always get you to the right place. Bingo.
Loopbacks
IP addresses don’t always have to refer to remote hosts. Especially in development you often want to connect to sockets on your local host.
Most systems define a loopback interface. This is an entirely virtual interface and, unlike the interface to your network card, is not attached to any hardware. Any data sent to the loopback interface is immediately received on the same interface. With a loopback address your network is constrained to the local host.
The host name for the loopback interface is officially called localhost
and the loopback IP address is typically 127.0.0.1
. These are defined in a ‘hosts’ file for your system.
IPv6
I’ve mentioned IPv4 a few times, but have neglected to mention IPv6. IPv6 is an alternative addressing scheme for IP addresses.
Why does it exist? Because we literally ran out of IPv4 addresses. IPv4 consists of four numbers each in the range of 0-255. Each of these four numbers can be represented with 8 bits, giving us 32 bits total in the address. That means there are 232 or 4.3 billion possible addresses. This a large number, but you can imagine how many individual devices are connected to networks that you see every day… it’s no wonder we’re running out.
So IPv6 is a bit of an elephant in the room at the moment. With IPv4 addresses now being exhausted, IPv6 is necessarily becoming relevant. It has a different format that allows for an astronomical number of unique IP addresses.
But for the most part you don’t need to be typing these things out by hand and the interaction with either addressing scheme will be identical.
Ports
There’s one more aspect that’s crucial to an endpoint: the port number. Continuing with our phone call example: if you want to have a conversation with someone in an office building you’ll have to call their phone number, then dial their extension. The port number is the ‘extension’ of a socket endpoint.
The combination of IP address and port number must be unique for each socket. Thus, you could have one socket with an IPv4 address listening on the same port number as a socket with an IPv6 address, but you couldn’t have two sockets with the same IPv4 address listening on the same port number.
Without ports, a host would only be able to support one socket at a time. By marrying each active socket to a specific port number, a host is able to support thousands of sockets concurrently.
This problem is solved not with DNS, but with a list of well-defined port numbers.
For example, HTTP communication happens on port 80 by default, FTP communication on port 21. There is actually an organization responsible for maintaining this list. More on port numbers in the next chapter.
Creating Your Second Socket
Now we get to see the first bit of syntactic sugar that Ruby offers.
Although there are much higher-level abstractions than this for creating sockets Ruby also lets you represent the different options as symbols instead of constants. So Socket::AF_INET
becomes :INET
and Socket::SOCK_STREAM
becomes :STREAM
. Here’s an example of creating a TCP socket in the IPv6 domain:
require 'socket'
socket = Socket.new(:INET6, :STREAM)
This creates a socket, but it’s not yet ready to exchange data with other sockets. The next chapter will look at taking a socket like this and preparing it to do actual work.
Docs
Now seems like a good time to bring up documentation. One nice thing about doing socket programming is that you already have lots of documentation on your machine that can help you out. There are two primary places to find documentation for this stuff: 1) manpages, and 2) ri.
Let’s do a quick review of each in turn.
-
Unix Manual pages will provide documentation about underlying system functions (C code). These are the primitives that Ruby’s socket library is built upon. The manpages are thorough, but very low-level. They can give you an idea of what a system call does where Ruby’s docs are lacking. It can also tell you what error codes are possible.
For example, in the code sample above we used
Socket.new
. This maps to a system function calledsocket()
which creates a socket. We can see the manpage for this using the following command:$ man 2 socket
Notice the
2
? This tells theman
program to look in section 2 of the manpages. The entire set of manpages is divided into sections.- Section 1 is for ‘General Commands’ (shell programs)
- Section 2 is for system calls
- Section 3 is for C library functions
- Section 4 is for ‘Special Files’
- Section 5 is for ‘File Formats’
- Section 7 provides overviews on various topic. tcp(7) is of interest.
I’ll refer to manpages using this syntax: socket(2). This refers to the socket manpage in section 2. This is necessary because some manpages exist in multiple sections. Take stat(1) and stat(2) as an example.
If you have a look at the ‘SEE ALSO’ section of socket(2), you’ll see some of the other system calls we’ll be looking at.
-
ri is Ruby’s command line documentation tool. The Ruby installer installs documentation for the core library as part of the installation process.
Some parts of Ruby aren’t very well documented, but I must say that the socket library is pretty well covered. Let’s look at the
ri
docs forSocket.new
. We use the following command:$ ri Socket.new
ri is useful and doesn’t require an internet connection. It’s a good place to look if you need guidance or examples.
System Calls From This Chapter
Each chapter will list any new system calls that were introduced and show you were you can find out more about them using ri
or manpages.
Socket.new
-> socket(2).