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
- use the file system as the address space (everything in Unix is a file) e.g.
- 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;
- use the IP address and a port number as socket address, e.g.
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: IPv4AF_INET6: IPv6AF_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.
- use
- Client side:
- use
connect()system call to connect to a passive socket, using the same address.
- use
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?
- 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. - Database Connections: connecting to PostgreSQL or Redis locally via Unix Socket is often 20-30% faster than connecting via
localhost:5432because it skips the TCP overhead.