1. What is a Unix Domain Socket?
A Unix Domain Socket (UDS) is an inter-process communication (IPC) endpoint that allows data exchange between processes on the same host. Unlike network sockets, UDS utilizes the kernel’s memory to pass data, bypassing the entire network stack.
- In-Memory Speed: No TCP/IP headers, no routing, and no checksums.
- File Identity: It appears as a file (e.g.,
.sock) in the filesystem, allowing you to use standard file permissions for security.
2. How does the Connection Lifecycle work? (The Flow)
The connection follows a strict state machine managed by the OS kernel. Unlike a simple file write, both parties must be active and synchronized.
A. Establishment (The Handshake)
- Listen (Server): The server creates a socket file and waits. The kernel marks this file as “listening.”
- Dial/Connect (Client): The client specifies the file path. The kernel checks permissions.
- Accept (Server): The server “picks up” the connection. The kernel creates a dedicated communication channel for this specific pair.
B. Data Transfer
- Data is written to a buffer in the kernel.
- The kernel immediately notifies the receiving process that data is ready.
- Full-Duplex: Both processes can read and write simultaneously.
C. Connection Closure (The Teardown)
- Close (Initiator): One side sends a
FIN(finish) signal via the kernel. - EOF (Receiver): The other side receives an
EOF(End of File) and knows no more data is coming. - Cleanup: The processes close their file descriptors. Crucial: The server must manually delete the
.sockfile from the disk, or the next “Listen” attempt will fail.
3. Go Implementation: Server & Client
In Go, the net package treats Unix sockets as first-class citizens.
The Server (server.go)
package main
import (
"fmt"
"net"
"os"
)
func main() {
socketPath := "/tmp/demo.sock"
// 1. CLEANUP: Always remove old socket files
if _, err := os.Stat(socketPath); err == nil {
_ = os.Remove(socketPath)
}
// 2. LISTEN: Bind to the socket file
l, _ := net.Listen("unix", socketPath)
defer l.Close()
fmt.Println("Server listening...")
for {
// 3. ACCEPT: Wait for a client
conn, _ := l.Accept()
// 4. HANDLE: Run in a goroutine for concurrency
go func(c net.Conn) {
defer c.Close() // 5. CLOSE: Close the individual connection
buf := make([]byte, 1024)
n, _ := c.Read(buf)
fmt.Printf("Received: %s\n", string(buf[:n]))
c.Write([]byte("Message Received!"))
}(conn)
}
}
The Client (client.go)
package main
import (
"fmt"
"net"
)
func main() {
// 1. DIAL: Connect to the server's path
conn, _ := net.Dial("unix", "/tmp/demo.sock")
defer conn.Close() // 3. CLOSE: Teardown connection when done
// 2. WRITE/READ: Exchange data
conn.Write([]byte("Hello from Client"))
reply := make([]byte, 1024)
n, _ := conn.Read(reply)
fmt.Println("Server replied:", string(reply[:n]))
}
4. Why can’t I just use cat or echo?
If “everything is a file,” why does echo "hi" > /tmp/demo.sock fail?
- System Calls:
catandechouse theopen()system call meant for storage files. Sockets require thesocket()andconnect()system calls. - The Kernel Guard: The kernel recognizes the “S” attribute (Socket) on the file. If a process tries to open it without a proper socket handshake, the kernel returns an error (e.g.,
Device not configured).
[Image comparing file system I/O vs socket I/O architecture]
5. Key Comparison Table
| Feature | Regular File | Unix Socket | TCP Socket |
|---|---|---|---|
| I/O Method | cat / echo | nc -U / Go Code | nc / Go Code |
| Scope | Persistence on disk | Local IPC only | Network / Local |
| Lifecycle | Exists until deleted | File exists; Connection is transient | No file; Port-based |
| Speed | Slow (Disk I/O) | Fastest (Memory) | Medium (Network Stack) |
| Security | chmod / chown | chmod / chown | Firewall / IP Tables |
6. Best Practices for Go Developers
- Abstract Sockets (Linux Only): If you use
@nameas the path, the socket lives in memory only and has no physical file. This solves the “cleanup” problem entirely. - Permissions: After calling
net.Listen, useos.Chmod("/tmp/demo.sock", 0600)to ensure only your user can talk to the service. - Deadlines: Always set
conn.SetDeadline()in your Go server to prevent “zombie” connections from hanging your goroutines if a client crashes.
Would you like me to show you how to implement Abstract Sockets in Go so you never have to worry about os.Remove again?