logo

Linux / Unix - Sockets

Unix Domain Sockets vs Internet Domain Sockets

  • Unix Domain Sockets: a.k.a. IPC sockets, allow communication between 2 processes, i.e. the server and the client, on the same machine.
    • use the file system as the address space (everything in Unix is a file) e.g. /var/run/docker/containerd/containerd.sock
  • Internet Domain Sockets: allow communication over a network, i.e. the server and the client are on different machines.
    • use the IP address and a port number as socket address, e.g. 10.20.30.40:4444;

As you can see from the system call below, they are distinguished by the domain.

Communication

Two processes may communicate with each other if each obtains a socket.

  • The server process binds its socket to an address, opens a listen channel, and then continuously loops.
  • Inside the loop, the server process is put to sleep while waiting to accept a client connection.
  • Upon accepting a client connection, the server then executes a read system call that will block wait.
  • The client connects to the server's socket via the server's address.
  • The client process then writes a message for the server process to read.

At different levels:

  • Unix domain socket: data packets are passed between two connected processes via the transport layer — either TCP or UDP.
  • Internet domain socket: data are passed between two connected processes via the transport layer and the Internet Protocol (IP) of the network layer — either TCP/IP or UDP/IP.

System Calls

int socket(int domain, int type, int protocol);

Most important domains:

  • AF_INET: IPv4
  • AF_INET6: IPv6
  • AF_UNIX / AF_LOCAL: Unix Socket

Most common types:

  • SOCK_STREAM: a stream-oriented socket (TCP)
  • SOCK_DGRAM: a datagram-oriented socket (UDP)

protocol: specify the protocol. In most cases there's only one protocol for the specified type, use 0 for protocol.

Example:

fd = socket(AF_UNIX, SOCK_STREAM, 0);

After we create the sockets on both server and client sides:

  • Server side:
    • use bind() system call to bind it to an address.
    • use listen() system call to mark the socket as passive. (by default, the socket is active)
    • use accept() system call to accept an incoming connection.
  • Client side:
    • use connect() system call to connect to a passive socket, using the same address.

Then use read() and write() system calls to communicate with the peer socket.

Remember to call close() to close the sockets.

Commands

Read more: Shell Cheatsheet - Networking

Code Examples

A Unix Domain Socket (often just called a Unix Socket) allows two processes on the same machine to talk to each other.

Unlike TCP/IP sockets (which use an IP address and Port), Unix sockets use a File Path on the filesystem. This makes them faster and more secure for local communication because they bypass the network stack entirely.

Here is a clear example using Python, followed by how to test it with command-line tools.

1. The Python Server (The Listener)

This script creates a socket file at /tmp/demo.sock and waits for a message.

# server.py
import socket
import os

# 1. Define the file path for the socket
SOCKET_PATH = "/tmp/demo.sock"

# 2. Cleanup: Make sure the file doesn't already exist
if os.path.exists(SOCKET_PATH):
    os.remove(SOCKET_PATH)

# 3. Create the socket
#    AF_UNIX  = Unix Domain Socket (Local)
#    AF_INET  = Internet Socket (IP)
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

# 4. Bind to the file path
server.bind(SOCKET_PATH)

# 5. Listen for incoming connections
server.listen(1)
print(f"Listening on {SOCKET_PATH}...")

try:
    while True:
        # Accept a connection
        connection, client_address = server.accept()
        try:
            print("Connection accepted!")

            # Read data
            data = connection.recv(1024)
            if data:
                print(f"Received: {data.decode()}")

                # Send a response back
                response = f"Processed: {data.decode()}"
                connection.sendall(response.encode())
        finally:
            # Clean up the connection
            connection.close()
finally:
    # Clean up the socket file when server stops
    server.close()
    os.remove(SOCKET_PATH)

2. The Python Client (The Sender)

This script connects to that specific file path and sends data.

# client.py
import socket
import sys

SOCKET_PATH = "/tmp/demo.sock"

# 1. Create the socket
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

try:
    # 2. Connect to the server's file path
    print(f"Connecting to {SOCKET_PATH}...")
    client.connect(SOCKET_PATH)

    # 3. Send data
    message = "Hello from Unix Socket!"
    client.sendall(message.encode())

    # 4. Receive response
    data = client.recv(1024)
    print(f"Server replied: {data.decode()}")

finally:
    client.close()

3. How to verify it works (The "Aha!" Moment)

Step A: Run the Server

Open a terminal and run the server. It will hang, waiting for a connection.

python3 server.py
# Output: Listening on /tmp/demo.sock...

Step B: Look at the file system

Open a second terminal. You will see the socket actually exists as a file. The s at the start of the permissions line indicates it is a socket.

ls -l /tmp/demo.sock

# Output:
# srwxr-xr-x  1 user  group  0 Jan 20 10:00 /tmp/demo.sock

Step C: Connect using netcat (No Python needed)

You can use the standard tool nc (netcat) with the -U flag to talk to Unix sockets directly.

echo "Hello from Netcat" | nc -U /tmp/demo.sock

# Output:
# Processed: Hello from Netcat

Key Differences in Code: TCP vs. Unix

The code is almost identical, except for two lines:

Feature TCP Socket (Standard) Unix Domain Socket (Optimized)
Family socket.AF_INET socket.AF_UNIX
Address ("127.0.0.1", 8080) "/tmp/demo.sock"
Cleanup OS frees port automatically You must delete the file (rm)
Permissions Hard to restrict (iptables) Easy (Standard file chmod 700)

When should you use this?

  1. Docker/Kubernetes: When you mount the Docker socket (/var/run/docker.sock) into a container, you are using this exact mechanism to let the container talk to the host daemon.
  2. Database Connections: connecting to PostgreSQL or Redis locally via Unix Socket is often 20-30% faster than connecting via localhost:5432 because it skips the TCP overhead.