net: Dial, Listen, and UDP
net: Dial, Listen, and UDP
Overview
Package net is the foundation under net/http: TCP/UDP addresses, listeners, dialers, and resolution. When HTTP is too high-level—or you need a custom protocol—you work here.
TCP services deep dive: 131 TCP services.
Addresses and resolution
// TCPAddr, UDPAddr, UnixAddr
tcpAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:9000")
ips, err := net.LookupIP(ctx, "example.com") // prefer context variants where availablePrefer net.Dialer over bare net.Dial so you can set timeouts and keep-alives.
d := net.Dialer{
Timeout: 3 * time.Second,
KeepAlive: 30 * time.Second,
}
conn, err := d.DialContext(ctx, "tcp", "127.0.0.1:9000")TCP server (line protocol sketch)
ln, err := net.Listen("tcp", "127.0.0.1:9000")
if err != nil {
log.Fatal(err)
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
log.Println("accept:", err)
continue
}
go handle(conn)
}
func handle(c net.Conn) {
defer c.Close()
_ = c.SetDeadline(time.Now().Add(30 * time.Second))
br := bufio.NewReader(c)
line, err := br.ReadString('\n')
if err != nil {
return
}
_, _ = io.WriteString(c, "echo: "+line)
}| Habit | Why |
|---|---|
| Deadline per connection | Avoid stuck clients forever |
| One goroutine per conn (or limited pool) | Bound resource use |
Always Close |
Prevent FD leaks |
bufio for line protocols |
Fewer syscalls |
TCP client
conn, err := d.DialContext(ctx, "tcp", addr)
if err != nil {
return err
}
defer conn.Close()
_ = conn.SetDeadline(time.Now().Add(5 * time.Second))
_, err = io.WriteString(conn, "ping\n")
// read response...UDP
// listen
pc, err := net.ListenPacket("udp", "127.0.0.1:9001")
// or DialUDP for connected UDP
buf := make([]byte, 1500)
n, addr, err := pc.ReadFrom(buf)
_, err = pc.WriteTo(buf[:n], addr) // echoUDP is message-oriented: no streams, no automatic retransmit. Size buffers for your MTU/use case.
Unix domain sockets
ln, err := net.Listen("unix", "/tmp/app.sock")
// client: Dial("unix", "/tmp/app.sock")Useful for local IPC (sidecar, same-host tools). Remember file permissions on the socket path.
net.Conn as io.Reader / Writer
// stream copy with limit
n, err := io.Copy(dst, io.LimitReader(conn, 1<<20))Composition with io is the Go style: your protocol parsers should take io.Reader/io.Writer when possible, not only net.Conn.
Errors and temporary failures
var ne net.Error
if errors.As(err, &ne) && ne.Timeout() {
// retry or return 504-style
}DNS and dial failures are often wrapped—log with %+v / slog attributes; branch with errors.As.
ip and ipnet helpers
ip := net.ParseIP("2001:db8::1")
_, cidr, _ := net.ParseCIDR("10.0.0.0/8")
ok := cidr.Contains(net.ParseIP("10.1.2.3"))Handy for allowlists and basic network policy in tools.
Rules of thumb
| Do | Don’t |
|---|---|
| Dial/Listen with timeouts/deadlines | Block forever on Accept/Read |
Propagate context on Dial |
Ignore cancel when user hits Ctrl-C |
| Limit concurrent handlers | Accept unbounded go handle under load without a cap |
| Close connections | Leak FDs on error paths |
Try next
- Build a TCP echo server and client with 2s deadlines.
- Send a UDP packet to yourself and print the peer address.
- Wrap
handlewith a semaphore channel of size 100 and load-test Accept.