skip to content

Datagrams and Unix Sockets

net.ListenPacket hands you a PacketConn where each ReadFrom is one whole datagram plus its sender, and net.Listen over a unix network serves the same http.Server through a socket file.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

questions

5

In Go, what does net.ListenPacket give you, and how does PacketConn.ReadFrom differ from reading a stream?

level: juniorimportance: must knowfreq 46%

answer

  1. no listener, no connections
  2. one call, one whole message
  3. the read tells you who sent it
  4. reply with WriteTo to that address
  5. ListenUDP for the concrete type

basics

~20 s

net.ListenPacket binds a datagram socket and returns a net.PacketConn. There is nothing to accept: one ReadFrom call returns exactly one whole datagram plus the sender's address, while a stream read returns whatever bytes happen to have arrived.

solid answer

~40 s

`net.ListenPacket("udp", ":8125")` binds a UDP port and hands back a `net.PacketConn`. A datagram socket is connectionless, so there is no listener and no per-peer connection object: one socket serves every peer. `ReadFrom(buf)` returns `(n, addr, err)` where `buf[:n]` is exactly one datagram and `addr` says who sent it, so you answer that peer with `WriteTo(reply, addr)`. Message boundaries are preserved, which means you never write framing code — but you also get no ordering, no retransmission and no delivery guarantee. A stream connection is the opposite: `Read` gives you an arbitrary slice of a byte river and you frame it yourself. If you need UDP-specific methods, `net.ListenUDP` returns the concrete `*net.UDPConn` (`ReadFromUDP`, `SetReadBuffer`); `net.ListenPacket` also opens `"unixgram"` sockets.

code

go · 15 lines
go
pc, err := net.ListenPacket("udp", ":8125")
if err != nil {
	return err
}
defer pc.Close()

buf := make([]byte, 1500)
for {
	n, addr, err := pc.ReadFrom(buf)
	if err != nil {
		return err
	}
	// buf[:n] is exactly one datagram, sent by addr
	handleMetric(buf[:n], addr)
}

go deeper

for a junior

Be ready to say that net.ListenPacket returns a net.PacketConn, that there is nothing to accept, and that ReadFrom hands you one whole datagram plus the sender's address.

for a middle

Explain why message boundaries mean no framing code and no partial reads, and when you would use net.ListenUDP's concrete type instead of the PacketConn interface.

for a senior

Show the judgment behind choosing a datagram protocol at all: what loss costs you, why the read loop must stay tight, and why buf[:n] must be copied before it leaves the loop.

for a principal

Own the call on which internal traffic may be lossy. Argue when fire-and-forget telemetry is the right contract and when a service needs a stream or an acknowledged protocol instead.

## Two shapes of socket, two constructors Go's `net` package splits socket creation along the connection-oriented / connectionless line, and the split shows up in which constructor you call and which interface you get back. - **Connection-oriented** (`"tcp"`, `"unix"`, `"unixpacket"`): `net.Listen` returns a `net.Listener`, and each peer becomes its own `net.Conn` with `Read`/`Write`. - **Connectionless** (`"udp"`, `"unixgram"`): `net.ListenPacket` returns a `net.PacketConn`. There is no listener, no handshake, and no per-peer object. One socket is the whole server. ```go pc, err := net.ListenPacket("udp", ":8125") ``` The returned value satisfies: ```go type PacketConn interface { ReadFrom(p []byte) (n int, addr Addr, err error) WriteTo(p []byte, addr Addr) (n int, err error) Close() error LocalAddr() Addr // plus deadline methods } ``` ## What ReadFrom actually does `ReadFrom` copies **one datagram** out of the socket's receive queue into your slice and returns how many bytes it copied together with the sender's address. That is the whole difference from a stream read, and almost every UDP bug in a Go service traces back to forgetting it: - **One call, one message.** If three datagrams are queued, three `ReadFrom` calls return them one at a time. They are never concatenated, and Go never merges or splits them. - **The message is complete or it is cut.** A stream read returning fewer bytes than you asked for means "more later". A datagram read returning fewer bytes than the datagram contained means the rest was thrown away by the kernel — there is no "later" for that message. - **The sender comes with the message.** Because the socket is not connected to anybody, the only way to know who to answer is the `addr` the read returns. You keep it if you need to reply. - **The address type is an interface.** `addr` is a `net.Addr`; for a UDP socket the dynamic type is `*net.UDPAddr`, for a `unixgram` socket `*net.UnixAddr`. You can pass it straight back to `WriteTo` without ever type-asserting it. ## A typical agent loop A statsd-style metrics agent is the canonical shape: a fire-and-forget protocol where each line is one datagram, senders never wait for a reply, and losing a message costs a data point rather than correctness. ```go buf := make([]byte, 1500) for { n, addr, err := pc.ReadFrom(buf) if err != nil { return err } handleMetric(buf[:n], addr) } ``` One buffer is reused for every read, because the read loop owns it. If you hand `buf[:n]` to another goroutine you must copy it first — the next `ReadFrom` overwrites the same array. ## Replying, and connected UDP sockets To answer a peer: ```go _, err := pc.WriteTo(reply, addr) ``` The same socket, the same port, addressed per message. If instead your program talks to exactly one remote — a client, not a server — you can *connect* a UDP socket with `net.Dial("udp", host)`. That returns an ordinary `net.Conn`: `Read` and `Write` work with no address argument because the kernel filters to that peer, and on Linux an ICMP port-unreachable from that peer surfaces as a `connection refused` error on a later read or write, which an unconnected socket never sees. ## When to reach for the concrete type `net.ListenUDP("udp", &net.UDPAddr{Port: 8125})` returns `*net.UDPConn` instead of the interface. You want it when you need something the interface does not expose: `ReadFromUDP` (which hands you `*net.UDPAddr` directly), `ReadMsgUDP` (out-of-band data and kernel flags), or `SetReadBuffer` (asking the kernel for a bigger receive queue). Accepting `net.PacketConn` in your own function signatures is still the better habit — it keeps the code testable and works unchanged for `"unixgram"`. ## What you give up Nothing in this API retransmits, orders, deduplicates or acknowledges. A datagram protocol is a design choice you make because loss is cheaper than latency — metrics, logs, discovery beacons, telemetry. If losing a message is not acceptable, either put the reliability in your own protocol or use a stream.

  • With no connection object per peer, how does the agent send a reply to the right sender?
    You keep the `addr` that `ReadFrom` returned and call `pc.WriteTo(reply, addr)` on the same socket. If your program only ever talks to one remote, `net.Dial("udp", host)` gives a connected `net.Conn` instead, where plain `Read` and `Write` work because the kernel filters traffic to that peer.
  • What does net.ListenUDP give you that net.ListenPacket does not?
    The concrete `*net.UDPConn` rather than the `net.PacketConn` interface, so you can call UDP-specific methods: `ReadFromUDP` (returns `*net.UDPAddr` with no type assertion), `ReadMsgUDP`, and `SetReadBuffer` to ask the kernel for a larger receive queue. Prefer `net.PacketConn` in your own signatures; reach for the concrete type only where you need those methods.
  • Can one ReadFrom call ever return two queued datagrams at once?
    No. A datagram read returns at most one message, no matter how many are queued or how much room the buffer has. Three queued datagrams take three `ReadFrom` calls. That is exactly why UDP needs no length prefix or delimiter framing, and why treating the socket like a byte stream corrupts your parsing.

A stream socket is a phone line you open to one caller; a datagram socket is a mailbox. Each letter arrives whole, with a return address on it, and you answer by posting one back.

saying these in an interview costs you the question

  • Looks for an Accept call on a UDP socket
  • Expects one read to return several queued datagrams merged
  • Assumes a short datagram read continues on the next read
  • Adds length-prefix framing to a UDP protocol out of habit
  • Thinks ListenPacket only works for UDP, not unixgram
open as a page

Why does a Go UDP agent reading into a 512-byte buffer silently lose part of a 1400-byte datagram?

level: middleimportance: should knowfreq 40%

basics

~20 s

A datagram read is all or nothing: ReadFrom copies at most len(buf) bytes and the kernel discards the rest of that message. You get n of 512, a nil error, and the other 888 bytes are gone.

open as a page

A Go agent serving HTTP over a Unix socket cannot rebind /run/agent.sock after a hard kill. Why, and what is the fix?

level: seniorimportance: should knowfreq 36%

basics

~20 s

A Unix listener's address is a file. Go unlinks it in the listener's Close, which a hard kill never runs, so the stale file survives and the next bind fails. Remove it only after checking nothing is live.

open as a page

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

level: middleimportance: nice to knowfreq 28%

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.

open as a page

A packet capture on the interface counts 50k UDP datagrams a minute but your Go agent logs 30k — where did the rest go?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Almost always the kernel's socket receive queue overflowed while the read loop was busy doing other work, so datagrams reached the host and were then discarded. Check the UDP drop counter, then keep the read loop empty.

open as a page