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)

  1. Listen (Server): The server creates a socket file and waits. The kernel marks this file as “listening.”
  2. Dial/Connect (Client): The client specifies the file path. The kernel checks permissions.
  3. 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)

  1. Close (Initiator): One side sends a FIN (finish) signal via the kernel.
  2. EOF (Receiver): The other side receives an EOF (End of File) and knows no more data is coming.
  3. Cleanup: The processes close their file descriptors. Crucial: The server must manually delete the .sock file 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: cat and echo use the open() system call meant for storage files. Sockets require the socket() and connect() 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

FeatureRegular FileUnix SocketTCP Socket
I/O Methodcat / echonc -U / Go Codenc / Go Code
ScopePersistence on diskLocal IPC onlyNetwork / Local
LifecycleExists until deletedFile exists; Connection is transientNo file; Port-based
SpeedSlow (Disk I/O)Fastest (Memory)Medium (Network Stack)
Securitychmod / chownchmod / chownFirewall / IP Tables

6. Best Practices for Go Developers

  • Abstract Sockets (Linux Only): If you use @name as the path, the socket lives in memory only and has no physical file. This solves the “cleanup” problem entirely.
  • Permissions: After calling net.Listen, use os.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?