← Back to list

The Linux Developer’s Toolbox: Networking

Understanding Sockets, Protocols, and High-Performance Communication From Packets to Applications: Networking in Linux

Kobi Toueg in The Thoughtful Engineer · 2026-07-23 07:37 · 1 claps · 6.4 min read paywalled
#networking #linux #sockets #epoll #tcp-ip
Open on Medium ↗
Wiki topics: 🔒 · Cybersecurity 🔓 · Open Source

The Linux Developer’s Toolbox: Networking

Continue reading the complete article here — free access.

Sockets

📖 What is it?

A socket is the standard Linux interface for communication between applications. It provides one endpoint of a communication channel through which processes can exchange data, whether they are running on the same machine or across a network.

Like many other resources in Linux, a socket is represented by a file descriptor, reinforcing the Linux philosophy that everything is a file. Once a socket is created, the application interacts with it through its file descriptor, using familiar operations such as reading, writing, and closing the communication channel. This unified design allows applications to work with sockets much like they do with regular files, while the kernel transparently handles the underlying communication mechanism.

One of the strengths of the socket API is its flexibility. When creating a socket, the application specifies:

  • the communication domain (for example, IPv4, IPv6, or Unix domain sockets),
  • the communication type (such as a reliable byte stream or datagrams),
  • and optionally the underlying protocol.

The same programming interface can therefore be used for many different communication mechanisms.

For example:

// IPv4 TCP socket
socket(AF_INET, SOCK_STREAM, 0);

// IPv4 UDP socket
socket(AF_INET, SOCK_DGRAM, 0);

// Unix domain socket
socket(AF_UNIX, SOCK_STREAM, 0);

A socket itself is neither a client nor a server. Both applications create sockets in the same way. An application becomes a server by binding the socket to an address and listening for incoming connections, while a client connects its socket to a server.

🛠️ What does it do?

Sockets provide a common API for communication regardless of the underlying transport.

The same set of system calls is used to establish communication and exchange data:

  • socket() – create a communication endpoint
  • bind() – associate it with an address
  • listen() – wait for incoming connections
  • accept() – accept a client connection
  • connect() – establish a connection to a server
  • send() / recv() – exchange data
  • close() – close the connection

Whether an application is communicating with a web server over TCP, sending DNS requests over UDP, or talking to another process through a Unix domain socket, the overall programming model remains remarkably similar.

💡 Why should every Linux developer know about it?

Sockets are one of the fundamental building blocks of modern Linux software.

Web servers, databases, container runtimes, cloud services, monitoring agents, and countless backend applications all communicate through sockets. Even applications that never access the Internet often use sockets to communicate with local services.

Understanding sockets also makes it easier to learn related Linux technologies. TCP and UDP are simply different communication protocols built on the same socket interface. Unix domain sockets provide efficient local inter-process communication using the same API. Even kernel communication through Netlink uses the socket interface.

Once you understand sockets, many seemingly different communication mechanisms in Linux reveal themselves as variations of the same underlying concept.

TCP vs UDP

📖 What is it?

TCP (Transmission Control Protocol) and UDP (User Datagram Protocol) are the two transport protocols most commonly used for communication over IP networks.

When creating an Internet socket, the application chooses which transport protocol it wants to use:

// TCP
socket(AF_INET, SOCK_STREAM, 0);

// UDP
socket(AF_INET, SOCK_DGRAM, 0);

Although both use the same socket API, they offer different communication models.

  • TCP provides a reliable, connection-oriented stream of bytes. It guarantees that data arrives intact, in the correct order, and without duplication.
  • UDP is connectionless and sends independent datagrams. It does not guarantee delivery, ordering, or retransmission, making it simpler and faster when reliability is not required.

🛠️ What does it do?

TCP establishes a connection between two applications before any data is exchanged. Once connected, the kernel handles many networking challenges automatically, including retransmitting lost packets, reordering packets that arrive out of sequence, detecting transmission errors, and regulating the flow of data between the sender and receiver. To the application, communication appears as a continuous, reliable stream of bytes.

UDP takes a different approach. It sends each datagram independently without first establishing a connection. The kernel does not guarantee that packets arrive, arrive only once, or arrive in the correct order. This greatly reduces overhead and latency, making UDP well suited for applications that can tolerate occasional packet loss or implement their own reliability mechanisms.

Typical use cases include:

TCP

  • Web browsing (HTTP/HTTPS)
  • Secure remote access (SSH)
  • Database communication
  • File transfers
  • Email protocols

UDP

  • DNS queries
  • Voice over IP (VoIP)
  • Live audio and video streaming
  • Online multiplayer games
  • Service discovery (Bonjour/mDNS)

💡 Why should every Linux developer know about it?

Nearly every networked application uses either TCP or UDP.

Choosing between them is one of the first design decisions when building a networked application.

TCP is usually the right choice when every byte matters and reliability is essential. UDP is often preferred when low latency is more important than guaranteed delivery, or when the application implements its own reliability mechanisms.

Even if you never write networking code directly, understanding the differences helps explain why applications behave differently under poor network conditions, why some services reconnect automatically, and why streaming video can continue smoothly even when a few packets are lost.

A useful rule of thumb is:

  • Choose TCP when correctness is more important than speed.
  • Choose UDP when speed and low latency are more important than perfect reliability.

Unix Domain Sockets

📖 What is it?

A Unix domain socket is a socket used for communication between processes running on the same Linux machine. Unlike TCP and UDP sockets, which communicate using IP addresses and network ports, Unix domain sockets communicate through a special file in the file system.

Creating one is almost identical to creating an Internet socket:

// TCP socket
socket(AF_INET, SOCK_STREAM, 0);

// Unix domain socket
socket(AF_UNIX, SOCK_STREAM, 0);

The same socket API is used — the only difference is the communication domain.

🛠️ What does it do?

Unix domain sockets provide fast and efficient inter-process communication (IPC) without involving the network stack. Since data never leaves the machine, there is no need for IP addressing, routing, or TCP packet processing.

Applications communicate by connecting to a socket file, for example:

/var/run/docker.sock

or

/tmp/my_service.sock

Many Linux services expose Unix domain sockets because they are both efficient and secure. Access can be controlled using the standard Linux file permission model.

Common examples include:

  • Docker daemon (docker.sock)
  • PostgreSQL
  • MySQL
  • systemd
  • Redis

💡 Why should every Linux developer know about it?

Many Linux applications communicate locally via Unix domain sockets rather than opening TCP ports. As a developer, you’ll frequently encounter .sock files while working with databases, containers, web servers, and system services.

Understanding Unix domain sockets also reinforces an important Linux concept: the socket API is independent of the underlying communication mechanism. By changing only the communication domain, the same programming interface can be used for communication across the Internet or between two processes on the same machine.

If two applications never need to communicate outside the local machine, a Unix domain socket is often the preferred choice because it is more efficient than using TCP over the loopback interface (the virtual network interface used for communication with the same machine, typically via localhost or 127.0.0.1).

epoll()

📖 What is it?

epoll() is a Linux I/O event notification mechanism designed to improve scalability when an application must wait for activity on multiple file descriptors simultaneously.

It is most commonly used with network sockets. Instead of creating one thread for each connection, an application can register thousands of sockets with an epoll instance and use a small number of threads to wait for activity on them.

epoll() is not limited to sockets. It can also monitor other event-producing file descriptors, such as pipes, timers, signal file descriptors, and event file descriptors.

🛠️ What does it do?

The application explicitly tells epoll() which file descriptors it wants to monitor.

A typical workflow is:

  1. Create an epoll instance with epoll_create1().
  2. Register sockets or other file descriptors using epoll_ctl().
  3. Wait for events using epoll_wait().
  4. Process only the file descriptors reported as ready.

The kernel may report events such as:

  • a socket has data available to read,
  • a socket is ready for writing,
  • a new client is waiting to be accepted,
  • a connection has been closed,
  • a pipe has received data,
  • or a timer has expired.

The application is not waiting for a complete message. It is waiting for a file descriptor to become ready for an operation.

This event-driven design allows one thread to wait efficiently on many connections:

int epfd = epoll_create1(0);

epoll_ctl(epfd, EPOLL_CTL_ADD, socket_fd, &event);

epoll_wait(epfd, events, max_events, timeout);

Instead of repeatedly checking every registered socket, the thread sleeps inside epoll_wait() until the kernel reports that one or more of them are ready.

Ordinary disk files are generally not a useful target forepoll(), because they usually do not behave like asynchronous event sources. epoll() is most valuable for resources where data or events may arrive unpredictably.

💡 Why should every Linux developer know about it?

epoll() is one of the main mechanisms Linux applications use to scale to large numbers of simultaneous connections.

Without an event-driven mechanism, a server might create one thread for each client. Thousands of mostly idle connections would then require thousands of thread stacks, more scheduler work, and frequent context switches.

With epoll(), one thread can efficiently wait on thousands of sockets and wake only when actual work is available. Larger applications may use several event-loop threads or combine epoll() with a worker pool, but they still avoid dedicating one thread to every connection.

Web servers, reverse proxies, databases, messaging systems, and asynchronous networking frameworks commonly rely on this model.

The key idea is:epoll() allows a small number of threads to efficiently manage a very large number of event-producing file descriptors.

🤝

Kobi Toueg Engineering Leader | AI, Security & System Architecture | Principal Software Engineering & Technical Leadership

LinkedIn: https://www.linkedin.com/in/kobi-toueg


메타데이터
post_id
1bdaec4eb429
slug
the-linux-developers-toolbox-networking-1bdaec4eb429
url
https://medium.com/the-thoughtful-engineer/the-linux-developers-toolbox-networking-1bdaec4eb429
canonical_url
https://medium.com/the-thoughtful-engineer/the-linux-developers-toolbox-networking-1bdaec4eb429
author_url
https://medium.com/@kobi.toueg
status
ok
fetched_at
2026-08-01 00:10:58