TCP vs Unix Sockets vs Named Pipes vs Shared Memory

TCP vs Unix Sockets vs Named Pipes vs Shared Memory

A side-by-side comparison of the four most common IPC mechanisms. None is universally best; choose based on access pattern, performance needs, and platform.

At a glance

TCP Unix sockets Named pipes (FIFO) Shared memory
Cross-machine ✅ yes ❌ no (local only) ❌ no ❌ no
Cross-OS ✅ universal ⚠️ Linux/macOS/BSD; Windows recent ✅ Unix + Windows (different APIs) ⚠️ POSIX shm or System V
Bidirectional ❌ unidirectional (need 2 for both ways) ✅ (with sync)
Stream / message stream (TCP) or message (UDP) both stream byte buffer
Latency ~15–30µs loopback ~5–10µs ~5–10µs ~50ns–1µs
Throughput ~1 GB/s ~5–10 GB/s ~5–10 GB/s RAM-bound
Auth/permissions none built-in filesystem perms filesystem perms filesystem perms
Multiple concurrent clients ✅ via accept loop ✅ via accept loop ❌ need extra plumbing ⚠️ via reader/writer count
Browser-accessible ✅ if HTTP ❌ (need bridge)
Complexity medium low low high (synchronisation)

TCP

The general-purpose network protocol. Works between any two machines that can route to each other, including loopback (127.0.0.1).

When to use:

When NOT to use:

For local IPC: TCP loopback is ~3× slower than Unix sockets and offers no security (any local process can connect). If you want HTTP for the developer ergonomics, layer it on Unix sockets via a bridge.

Unix domain sockets

Same socket API as TCP, but the address is a filesystem path (or abstract name on Linux). Communication stays in the kernel.

When to use:

When NOT to use:

This is the right default for local daemons, including mxr, lazydap, Docker, dbus, X11, postgres' default config, and most modern Linux/macOS local services.

See Unix Domain Sockets for depth.

Named pipes (FIFOs)

A unidirectional byte stream identified by a filesystem path. mkfifo /tmp/myfifo creates one; one process writes to it, another reads.

When to use:

When NOT to use:

Windows equivalent: "Named pipes" on Windows are different — bidirectional, multi-instance, more like Unix sockets. Same name, different thing.

Modern code rarely uses POSIX FIFOs. Use Unix sockets or full IPC instead.

Shared memory

Multiple processes map the same memory region. Reads and writes touch the same physical pages.

When to use:

When NOT to use:

Implementation: POSIX shm_open + mmap is the modern way. System V shmget is older, more cumbersome, still works.

For mxr / lazydap and most local daemons, shared memory is overkill. The IPC volume is small; messages-per-second are in the hundreds at most. Unix sockets have plenty of headroom.

Choosing for new projects

Decision tree:

  1. Cross-machine? → TCP, layer your protocol on top.
  2. Same machine, multiple clients, browser too? → Unix socket + HTTP bridge.
  3. Same machine, multiple clients, no browser? → Unix socket + your own protocol.
  4. Same machine, two known processes, simple pipeline? → Named pipe (or stdio if parent-child).
  5. Same machine, ridiculous throughput needed? → Shared memory + ring buffer.
  6. Different processes spawned by one parent, byte stream is fine? → anonymous pipes (pipe(2)), pass to child via fork.

For 90% of "I'm building a local daemon" cases, the answer is "Unix socket." mxr, lazydap, Docker, postgres, sshd-agent, cups, dbus all chose this. Don't overthink.

What mxr and lazydap chose, and why

Both: Unix domain sockets, stream type, length-prefixed JSON framing.

Reasoning:

The choice that most local daemons make. Worth knowing the alternatives, but the default is right.

See also