02. Socket Programming Concepts

Let’s start with high-level concepts. Basic networking knowledge is assumed.

2.1 What is a Socket?

A socket is a channel for 2 parties to communicate over a network. The name “socket” has nothing to do with the sockets on the wall.

Client-Server Model

For many use cases, such as Redis, the one initiating the channel is called a “client”, and the “server” is the one waiting for new clients.

A non-toy server can serve multiple clients simultaneously, which is what we will focus on.

Request-Response Model

Many client-server applications are also request-response:

Examples of request-response: DNS, HTTP/1.1, and Redis.

TCP, UDP and Applications

There are 2 types of channels: packet-based and byte-stream-based. Each offers a different set of capabilities to applications.

UDP is packet-based. A packet is a message of a certain size from the application’s PoV. But Redis is TCP-based, so we’ll ignore UDP.

TCP provides a continuous stream of bytes. Unlike messages, a byte stream has no boundaries within it, which is a major difficulty in understanding TCP! That’s why it’s important to code your own Redis.

2.2 TCP/IP Quick Review

Layers of Protocols

Network protocols are divided into different layers, where the higher layer depends on the lower layer, and each layer provides different capabilities.

  /\    | App |     message or whatever
  ||    | TCP |     byte stream
  ||    | IP  |     packets
  ||    | ... |

The layer below TCP is the IP layer. Each IP packet is a message with 3 components:

TCP: Reliable Byte Streams

Communication with a packet-based scheme is not easy. There are lots of problems for applications to solve:

To make things simple, the next layer is added on top of IP packets. TCP provides:

A byte stream is simply an ordered sequence of bytes. A protocol, rather than the application, is used to make sense of these bytes. Protocols are like file formats, except that the total length is unknown and the data is read in one pass.

UDP is on the same layer as TCP, but is still packet-based like the lower layer. UDP just adds port numbers over IP packets.

2.3 Socket Primitives

There are some concepts that you must know regardless of the language/platform you are targeting.

Applications Refer to Sockets by Opaque OS Handles

In the same way that Twitter handles are used to refer to Twitter users.

On Linux, a handle is called a file descriptor (fd), which is an integer that is unique to the process. The name “file descriptor” is just a name; it has nothing to do with files, nor does it describe anything.

On Linux, the socket() syscall allocates and returns a socket fd, which is used later to create a communication channel.

A handle must be closed when you’re done to free the associated resources on the OS side.

Listening Socket & Connection Socket

A TCP server listens on a particular address (IP + port) and accepts client connections from that address. The listening address is also represented by a socket fd. And when you accept a new client connection, you get the socket fd of the TCP connection.

2 types of socket handles:

  1. Listening sockets. Obtained by listening on an address.
  2. Connection sockets. Obtained by accepting a client connection from a listening socket.

The relevant syscalls on Linux:

In pseudo code:

fd = socket()
bind(fd, address)
while True:
    conn_fd = accept(fd)

Read and Write

Sending and receiving is also called reading and writing. There are many Linux syscalls with different arguments.

Reading Writing Description
read write Read/write with a single continuous buffer.
readv writev Read/write with multiple buffers.
recv send Has an extra flag.
recvfrom sendto Also get/set the remote address (packet-based).
recvmsg sendmsg readv/writev with more flags and controls.
recvmmsg sendmmsg Multiple recvmsg/sendmmsg in 1 syscall.

read() and write() are the most basic interfaces and will suffice for this book. You can ignore the others as they are for additional controls and optimizations.

Connect From a Client

The connect() syscall is for initiating a TCP connection from the client side. Pseudo code:

fd = socket()
connect(fd, address)

The type of a socket (listening or connection) is determined after the listen() or connect() syscall.

Summary: List of Socket Primitives

The next chapter will help you get started with real code.