skip to content

What is the difference between the "unix" and "unixgram" network names in Go's net package?

level: middleimportance: nice to knowfreq 28%

answer

  1. one is a river, one is letters
  2. which constructor takes which name
  3. SOCK_STREAM against SOCK_DGRAM
  4. net.Listen will not accept one of them
  5. local datagrams block instead of dropping

basics

~10 s

"unix" is a connection-oriented byte stream you open with net.Listen or net.Dial; "unixgram" is a datagram socket with message boundaries that you open with net.ListenPacket or net.ListenUnixgram. net.Listen rejects "unixgram" outright.

solid answer

~40 s

Both address a socket by filesystem path, but they are different socket types. `"unix"` is `SOCK_STREAM`: connection-oriented, ordered, reliable, and boundary-free, so you frame messages yourself — it is the local twin of TCP and what `net.Listen("unix", path)` gives you. `"unixgram"` is `SOCK_DGRAM`: each write is one message, each `ReadFrom` returns exactly one, and it is opened through `net.ListenPacket("unixgram", path)` or `net.ListenUnixgram`, because `net.Listen` only takes connection-oriented networks and returns an unknown-network error for it. Unlike UDP, local datagrams are not dropped for congestion: on Linux the sender blocks when the receiver's queue is full, so a slow reader becomes backpressure rather than loss. Truncation still applies — a short buffer cuts the message. There is also `"unixpacket"` (`SOCK_SEQPACKET`, Linux), connection-oriented but boundary-preserving.

code

go · 15 lines
go
addr := &net.UnixAddr{Name: "/run/metrics-agent.ctl", Net: "unixgram"}
pc, err := net.ListenUnixgram("unixgram", addr)
if err != nil {
	return err
}
defer pc.Close()

buf := make([]byte, 4096)
for {
	n, from, err := pc.ReadFromUnix(buf)
	if err != nil {
		return err
	}
	handleCommand(buf[:n], from) // buf[:n] is one whole command
}

go deeper

for a junior

Know that both names address a socket by filesystem path, that unix is a byte stream and unixgram keeps message boundaries, and that they are opened by different constructors.

for a middle

Explain the SOCK_STREAM against SOCK_DGRAM mapping, why net.Listen rejects unixgram, and why a unix stream needs your own framing while unixgram does not.

for a senior

Justify the choice for a real local channel: framing cost, per-client connection state, and the fact that a full unixgram queue blocks the sender instead of dropping, which turns a slow reader into caller latency.

for a principal

Own the local IPC contract between processes you ship together, including whether a control channel should apply backpressure or shed load, and what that means for the callers you cannot change.

## Three names, three socket types Go exposes the Unix-domain socket family under three network names, and they map one-to-one onto the kernel's socket types: | network | socket type | boundaries | connection | constructor | |---|---|---|---|---| | `"unix"` | `SOCK_STREAM` | no | yes | `net.Listen` / `net.Dial` / `net.ListenUnix` | | `"unixgram"` | `SOCK_DGRAM` | yes | no | `net.ListenPacket` / `net.ListenUnixgram` | | `"unixpacket"` | `SOCK_SEQPACKET` | yes | yes | `net.Listen` / `net.Dial` (Linux) | The addressing is the same for all three — a path on the filesystem, or on Linux a name in the abstract namespace written with a leading `@`, which has no filesystem entry at all. ## "unix": the local stream `net.Listen("unix", "/run/agent.sock")` returns a `net.Listener` and each peer becomes a `net.Conn`. Semantically it is TCP without the network: reliable, ordered, flow-controlled, and with **no message boundaries**. Three separate 10-byte writes may arrive as one 30-byte read, or as reads of 7 and 23 bytes. Anything with a message shape needs its own framing — a length prefix, a delimiter, or an existing protocol such as HTTP. That last option is common: because `"unix"` gives you an ordinary `net.Listener`, an `http.Server` can serve over it unchanged, which is how a daemon exposes a local control API without ever opening a TCP port. ## "unixgram": local datagrams `net.ListenPacket("unixgram", "/run/agent.ctl")` returns a `net.PacketConn`, and `net.ListenUnixgram` returns the concrete `*net.UnixConn` with `ReadFromUnix` and `WriteToUnix`. Every `WriteTo` is one message; every `ReadFrom` returns exactly one, with the sender's `*net.UnixAddr`. What surprises people is that `"unixgram"` shares UDP's *framing* but not UDP's *unreliability*: - **No congestion loss.** The kernel never discards a local datagram to relieve pressure. When the receiver's queue is full the sender blocks — in Go, the goroutine calling `WriteTo` parks until space appears. A slow reader therefore stalls its senders instead of quietly losing their messages. - **Ordering holds** between a given pair of sockets. - **Truncation still bites.** A read into a buffer smaller than the message keeps the prefix and discards the rest, exactly as with UDP. - **A client that wants replies must bind.** An unbound sender has no address for the server to answer, so a request/response control protocol over `unixgram` needs each client to bind its own path (for example with `net.ListenUnixgram` on a temporary path, or `net.DialUnix` with a local address) and to clean it up afterwards. ## Why net.Listen refuses "unixgram" `net.Listen` is defined for connection-oriented networks only — `tcp`, `tcp4`, `tcp6`, `unix`, `unixpacket`. There is nothing to listen for on a connectionless socket: no handshake, no per-peer object, just a bound address that receives messages. Passing `"unixgram"` returns an error wrapping `net.UnknownNetworkError`. The mirror rule holds too: `net.ListenPacket` handles `udp*` and `unixgram`, not `unix`. This pairing is the single most useful thing to remember about the two names, because getting it wrong fails at startup with a message that reads like a typo rather than a design mistake. ## Choosing between them Reach for `"unix"` when the local channel carries a protocol that already has framing — HTTP, a line protocol, protobuf with a length prefix — or when you want per-client connections with independent lifetimes and per-connection state. Reach for `"unixgram"` when messages are small, self-contained commands, when you want the sender to be able to fire without a connection setup, and when preserved boundaries save you writing a framer. A metrics agent that accepts UDP metrics from the network and a small set of control commands from a local socket can reasonably use `"unixgram"` for the control path: each command is one datagram, no framing, and the local backpressure means a control message is never silently lost. Reach for `"unixpacket"` only when you genuinely need both a connection and message boundaries and you are Linux-only — it is rare in Go code. ## The shared caveat All three are files (unless you use the abstract namespace), which means the socket path has a lifecycle of its own: it survives a crash, its directory has to exist, and it must be cleaned up. That lifecycle is the same for stream and datagram Unix sockets and catches people equally in both.

  • Why can't you pass "unixgram" to net.Listen?
    `net.Listen` exists for connection-oriented networks, where a listener waits for peers to connect — `tcp`, `unix`, `unixpacket`. A datagram socket has no connections at all, only a bound address that receives messages, so it is created through `net.ListenPacket` or `net.ListenUnixgram`. Passing `"unixgram"` to `net.Listen` fails with an error wrapping `net.UnknownNetworkError`.
  • Does a unixgram message get dropped under load the way a UDP one does?
    No. The kernel does not discard local datagrams for congestion; when the receiving socket's queue is full the sending goroutine's `WriteTo` blocks until space frees up. A slow reader therefore applies backpressure to its senders rather than losing their messages. Truncation is the failure that still applies: a buffer smaller than the message keeps the prefix and discards the rest.
  • Can a unixgram client receive a reply without any setup?
    Not portably. The server can only answer an address it was given, and an unbound sender has none. A client that expects replies binds its own socket path — for example with `net.ListenUnixgram` on a temporary path, or `net.DialUnix` with a local address — and removes that path when it exits, or the files accumulate.

saying these in an interview costs you the question

  • Thinks Unix domain sockets are always datagram-based
  • Passes unixgram to net.Listen and expects a listener
  • Assumes a unix stream socket preserves message boundaries
  • Believes unixgram drops messages under load like UDP
  • Expects a unixgram client to get replies without binding