Full-duplex, bidirectional communication between processes — like a two-way pipe, but better.
Remember pipes? They only go one way. You write into one end, read from the other. If you want two-way communication, you need two pipes — and that gets messy fast.
Remember FIFOs? Same problem. One direction only. Plus they're stuck with the limitations of the file interface: open(), read(), write().
Unix domain sockets solve both problems. They give you a two-way communication channel between processes on the same machine. Send data in both directions. Use a proper client-server model. And the API is the same one used for network programming — so everything you learn here transfers directly to Internet sockets.
Compare one-way pipes with full-duplex Unix sockets. Click to send messages in each direction.
Unix domain sockets look like special files in the filesystem (just like FIFOs), but you don't use open() and read(). Instead, you use the sockets API: socket(), bind(), listen(), accept(), connect(), send(), recv().
The communication follows a client-server pattern:
The "address" of a Unix socket is a filesystem path. Both sides reference the same path to establish a connection. The key data structure is struct sockaddr_un:
c struct sockaddr_un { unsigned short sun_family; /* AF_UNIX */ char sun_path[108]; /* socket path */ };
AF_UNIX means "Unix domain" — communication between processes on the same machine via a filesystem path. AF_INET means "Internet domain" — communication over TCP/IP networks. The API is nearly identical; only the address structure differs.The first step for both server and client is creating a socket descriptor with socket():
c #include <sys/socket.h> #include <sys/un.h> int s; s = socket(AF_UNIX, SOCK_STREAM, 0);
| Argument | Purpose | Value |
|---|---|---|
| domain | Communication domain | AF_UNIX (local machine) |
| type | Socket type | SOCK_STREAM (reliable, ordered) or SOCK_DGRAM (datagrams) |
| protocol | Protocol (usually 0) | 0 (auto-select) |
socket() returns a file descriptor, just like open(). This descriptor is an endpoint — think of it as the phone itself, before you've dialed a number or picked up a ringing call.
SOCK_STREAM gives you a reliable, ordered byte stream — like TCP. Data arrives in the order sent, no losses. SOCK_DGRAM gives you individual messages (datagrams) with no ordering guarantee — like UDP. For IPC on the same machine, SOCK_STREAM is the common choice.errno. Always check. The example code in Beej's guide sometimes omits error checking for clarity, but production code should always handle errors.A server does five things: socket, bind, listen, accept, handle. Let's walk through each step.
Create the socket (you already know this from Chapter 2).
Associate the socket with a filesystem path:
c struct sockaddr_un local; local.sun_family = AF_UNIX; strcpy(local.sun_path, "/tmp/echo_socket"); unlink(local.sun_path); /* remove if exists */ int len = strlen(local.sun_path) + sizeof(local.sun_family); bind(s, (struct sockaddr *)&local, len);
unlink() before bind()! If the socket file already exists from a previous run, bind() will fail with EADDRINUSE. Unlinking removes the stale file so you can bind fresh.Tell the socket to start accepting incoming connections:
c listen(s, 5); /* backlog of 5 pending connections */
The second argument is the backlog — how many connections can queue up before you call accept(). Additional clients get ECONNREFUSED.
Block until a client connects, then return a new socket descriptor for that connection:
c struct sockaddr_un remote; int t = sizeof(remote); int s2 = accept(s, (struct sockaddr *)&remote, &t);
accept() returns a new socket descriptor s2. The original s keeps listening for more connections. You use s2 to communicate with this specific client. This is how servers handle multiple clients.Use send() and recv() on the new descriptor s2. When done, close(s2) and loop back to accept().
Step through the server setup process. Each click advances to the next system call.
The client side is much simpler. No bind(), no listen(), no accept(). Just create a socket and connect:
c int s; struct sockaddr_un remote; s = socket(AF_UNIX, SOCK_STREAM, 0); remote.sun_family = AF_UNIX; strcpy(remote.sun_path, "/tmp/echo_socket"); int len = strlen(remote.sun_path) + sizeof(remote.sun_family); connect(s, (struct sockaddr *)&remote, len); /* Now send() and recv() freely */ send(s, "Hello!", 6, 0); recv(s, buf, 100, 0); close(s);
sun_path as the server's bind(). This is how the two find each other. The server creates the socket file; the client connects to it. Think of the path as a phone number.| Call | Server | Client |
|---|---|---|
| socket() | Yes | Yes |
| bind() | Yes | No |
| listen() | Yes | No |
| accept() | Yes (returns new fd) | No |
| connect() | No | Yes |
| send()/recv() | Yes (on accepted fd) | Yes (on connected fd) |
| close() | Yes | Yes |
Beej's echo server waits for a connection, receives whatever the client sends, and echoes it right back. Here's the full program:
c #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #define SOCK_PATH "echo_socket" int main(void) { int s, s2, t, len; struct sockaddr_un local, remote; char str[100]; s = socket(AF_UNIX, SOCK_STREAM, 0); local.sun_family = AF_UNIX; strcpy(local.sun_path, SOCK_PATH); unlink(local.sun_path); len = strlen(local.sun_path) + sizeof(local.sun_family); bind(s, (struct sockaddr *)&local, len); listen(s, 5); for(;;) { int done, n; printf("Waiting for a connection...\n"); t = sizeof(remote); s2 = accept(s, (struct sockaddr *)&remote, &t); printf("Connected.\n"); done = 0; do { n = recv(s2, str, 100, 0); if (n <= 0) { if (n < 0) perror("recv"); done = 1; } if (!done) send(s2, str, n, 0); } while (!done); close(s2); } return 0; }
for(;;)). Each iteration: accept a client, echo messages until the client disconnects (recv returns 0), close the connection, wait for the next client.s (the listening socket) only for accept(). All data exchange happens on s2 (the connected socket). After closing s2, the server loops back and accepts on s again. The two descriptors serve completely different purposes.The client is simpler — no bind, no listen, no accept. Just connect and talk:
c #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #define SOCK_PATH "echo_socket" int main(void) { int s, t, len; struct sockaddr_un remote; char str[100]; s = socket(AF_UNIX, SOCK_STREAM, 0); remote.sun_family = AF_UNIX; strcpy(remote.sun_path, SOCK_PATH); len = strlen(remote.sun_path) + sizeof(remote.sun_family); connect(s, (struct sockaddr *)&remote, len); while(printf("> "), fgets(str, 100, stdin), !feof(stdin)) { send(s, str, strlen(str), 0); t = recv(s, str, 100, 0); if (t > 0) { str[t] = '\0'; printf("echo> %s", str); } else { printf("Server closed\n"); exit(1); } } close(s); return 0; }
./echos). Run the client in another (./echoc). Type something at the > prompt. The server echoes it back. Press Ctrl-D (EOF) to disconnect.What if you want a two-way pipe between a parent and child process, without all the server/client setup? That's what socketpair() is for. It creates two connected sockets in one call — no bind, no listen, no accept, no connect.
c #include <sys/socket.h> int sv[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
After this call, sv[0] and sv[1] are connected to each other. Write to sv[0], read from sv[1]. Write to sv[1], read from sv[0]. Bidirectional.
pipe(fd) gives you two descriptors, but one is read-only and one is write-only. socketpair(sv) gives you two descriptors that are both read-write. And since they're sockets, you can use send/recv flags.The typical pattern: call socketpair(), then fork(). The parent uses sv[0], the child uses sv[1]:
c int sv[2]; char buf; socketpair(AF_UNIX, SOCK_STREAM, 0, sv); if (!fork()) { /* Child: use sv[1] */ read(sv[1], &buf, 1); printf("child: read '%c'\n", buf); buf = toupper(buf); write(sv[1], &buf, 1); } else { /* Parent: use sv[0] */ write(sv[0], "b", 1); read(sv[0], &buf, 1); printf("parent: read '%c'\n", buf); wait(NULL); }
read()/write() instead of send()/recv() on socket descriptors — they're just file descriptors. Beej uses read/write in the socketpair example for simplicity, since the flags argument to send/recv is usually 0 anyway.Watch the full client-server interaction play out step by step. Type a message, watch it travel through the socket, and see the echo come back.
Click "Connect" to establish a connection, then send messages. The server echoes each one back.
Parent and child communicate over a socketpair. Send a character from parent, watch the child uppercase it and send it back.
You've now seen every IPC mechanism in the Unix toolkit. Let's put them all in perspective:
| Mechanism | Direction | Sync | Data Model | Best For |
|---|---|---|---|---|
| Pipes (Ch. 4) | One-way | Built-in | Byte stream | Parent-child, shell pipelines |
| FIFOs (Ch. 5) | One-way | Built-in | Byte stream | Unrelated processes, simple IPC |
| Msg Queues (Ch. 7) | One-way | Built-in | Typed messages | Priority messages, typed data |
| Shared Memory (Ch. 9) | Any | Manual | Raw memory | High-perf data sharing |
| mmap (Ch. 10) | Any | Manual | File-backed memory | File access, persistent sharing |
| Unix Sockets (Ch. 11) | Two-way | Built-in | Byte stream / datagram | Client-server, full-duplex |
AF_INET. Everything you learned here — socket, bind, listen, accept, connect, send, recv — transfers directly to TCP/IP programming. The only difference is the address structure (struct sockaddr_in instead of struct sockaddr_un). Check out Beej's Guide to Network Programming for the full story.| When you need... | Use... |
|---|---|
| Simple parent-child byte stream | pipe() |
| Byte stream between unrelated processes | FIFOs (mkfifo) |
| Discrete typed messages | Message queues (msgget) |
| Zero-copy shared data (volatile) | Shared memory (shmget) |
| Zero-copy shared data (persistent) | mmap (MAP_SHARED) |
| Two-way communication | Unix sockets |
| Quick full-duplex parent-child pipe | socketpair() |
| Network communication | Internet sockets (AF_INET) |
"Wouldn't it be grand if you could send data in both directions like you can with a socket? Well, hope no longer, because the answer is here: Unix Domain Sockets!" — Beej