Chapter 2: Go’s net Package

Updated

October 31, 2026

Chapter 2: Go’s net Package: Your Socket Programming Toolbox

The Go standard library provides the foundation for all network programming through the net package. Rather than exposing raw POSIX system calls (socket, bind, listen, accept, connect) directly, Go offers clean, composable interfaces that hide OS-level differences while preserving direct control over network parameters.

In this chapter, you will learn the core interfaces, factory functions, protocol-specific types, and address resolution helpers that make up your socket programming toolbox.


The net.Dial and net.Listen Functions

At the highest level, the net package exposes two fundamental entry points: net.Listen for servers and net.Dial for clients.

1. net.Listen: Binding and Listening for Inbound Connections

Servers create a listening socket using net.Listen:

func Listen(network, address string) (Listener, error)
  • network: The protocol family. Common options include "tcp", "tcp4", "tcp6", "unix", and "unixpacket".
  • address: The host and port to bind to (e.g., "127.0.0.1:8080", ":8080" for all local interfaces, or "/tmp/service.sock" for Unix sockets).
ln, err := net.Listen("tcp", ":8080")
if err != nil {
    log.Fatalf("failed to bind port 8080: %v", err)
}
defer ln.Close()

The returned object satisfies the net.Listener interface:

type Listener interface {
    Accept() (Conn, error)
    Close() error
    Addr() Addr
}

2. net.Dial: Connecting to a Remote Endpoint

Clients initiate outbound connections using net.Dial:

func Dial(network, address string) (Conn, error)
conn, err := net.Dial("tcp", "example.com:80")
if err != nil {
    log.Fatalf("failed to dial: %v", err)
}
defer conn.Close()

Modern Idiom: net.Dialer and net.ListenConfig

In modern Go code (Go 1.11+), prefer net.Dialer and net.ListenConfig over bare Dial and Listen because they support timeouts, keep-alive configuration, and first-class context.Context cancellation:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

var dialer net.Dialer
conn, err := dialer.DialContext(ctx, "tcp", "example.com:443")
if err != nil {
    log.Fatalf("connection failed: %v", err)
}
defer conn.Close()

The Conn Interface Explained

The net.Conn interface is the heart of Go’s stream-oriented networking. Any bidirectional stream—whether an unencrypted TCP socket, a TLS connection, a Unix domain stream, or an in-memory pipe—implements net.Conn.

type Conn interface {
    Read(b []byte) (n int, err error)
    Write(b []byte) (n int, err error)
    Close() error
    LocalAddr() Addr
    RemoteAddr() Addr
    SetDeadline(t time.Time) error
    SetReadDeadline(t time.Time) error
    SetWriteDeadline(t time.Time) error
}

Key Behavioral Rules of net.Conn

  1. io.Reader and io.Writer Compatibility: Because net.Conn satisfies standard io.Reader, io.Writer, and io.Closer interfaces, you can pass any network connection directly into standard library utilities like io.Copy, bufio.NewReader, bufio.NewWriter, json.NewDecoder, or tls.Server.

  2. Concurrent Safety: Go’s net.Conn is safe for concurrent access by multiple goroutines. Specifically, one goroutine may safely call Read() while another goroutine concurrently calls Write(). However, concurrent multiple readers or concurrent multiple writers will interleave their bytes unpredictably.

  3. Blocking vs. Netpoller: When you call conn.Read(buf), execution appears to block until data arrives, an error occurs, or the deadline expires. Underneath, Go’s runtime netpoller deschedules the calling goroutine, freeing the OS thread to do other work until the kernel signals readiness.

  4. EOF Semantics: When the remote peer cleanly terminates the connection (sends a TCP FIN packet), subsequent calls to Read() will consume any remaining buffered data and then return io.EOF.


TCP-Specific Types and Methods

When you need granular control over TCP options that are not exposed by the generic net.Conn or net.Listener interfaces, you can use type assertions or concrete types: *net.TCPConn and *net.TCPListener.

ln, err := net.Listen("tcp", ":8080")
if err != nil {
    log.Fatal(err)
}
tcpLn := ln.(*net.TCPListener)

conn, err := tcpLn.AcceptTCP()
if err != nil {
    log.Fatal(err)
}

Important Methods on *net.TCPConn

  • SetNoDelay(noDelay bool) error: Enables or disables Nagle’s algorithm (TCP_NODELAY). By default, Go enables SetNoDelay(true), meaning small packets are dispatched immediately without buffering.
  • SetKeepAlive(keepalive bool) error: Enables or disables kernel-level TCP keep-alive probe packets.
  • SetKeepAlivePeriod(d time.Duration) error: Configures the interval between successive keep-alive probes.
  • SetReadBuffer(bytes int) error / SetWriteBuffer(bytes int) error: Sets the size of the kernel-level socket receive and send buffers (SO_RCVBUF, SO_SNDBUF).
  • CloseRead() error / CloseWrite() error: Shuts down only the read side or write side of a TCP connection (half-closing the TCP connection via FIN).

UDP-Specific Types and Methods

Because UDP is connectionless and datagram-oriented, it requires different semantics. Instead of net.Listener, UDP uses net.PacketConn and the concrete *net.UDPConn type.

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
    SetDeadline(t time.Time) error
    SetReadDeadline(t time.Time) error
    SetWriteDeadline(t time.Time) error
}

Listening on UDP

pconn, err := net.ListenPacket("udp", ":9000")
if err != nil {
    log.Fatal(err)
}
defer pconn.Close()

buf := make([]byte, 1500) // Standard MTU size
for {
    n, remoteAddr, err := pconn.ReadFrom(buf)
    if err != nil {
        return
    }
    // Echo back to sender
    _, _ = pconn.WriteTo(buf[:n], remoteAddr)
}

Connected UDP Sockets

If you call net.Dial("udp", "192.0.2.1:9000"), Go returns a *net.UDPConn bound to a single remote address. In this mode, you can call standard Read() and Write() methods instead of ReadFrom() and WriteTo(), and the kernel will discard incoming datagrams from any other source.


Resolving Addresses with net.ResolveTCPAddr and Friends

The net package provides address resolution utilities that parse string addresses and perform DNS lookups:

// Resolve a TCP address:
tcpAddr, err := net.ResolveTCPAddr("tcp", "golang.org:80")
if err != nil {
    log.Fatalf("DNS resolution error: %v", err)
}
fmt.Printf("IP: %s, Port: %d, Zone: %s\n", tcpAddr.IP, tcpAddr.Port, tcpAddr.Zone)

// Resolve a UDP address:
udpAddr, err := net.ResolveUDPAddr("udp", "time.nist.gov:123")

// Resolve a Unix domain socket address:
unixAddr, err := net.ResolveUnixAddr("unix", "/tmp/app.sock")

Parsing Host and Port Strings Safely

Never parse host and port strings using naive strings.Split(addr, ":") because IPv6 addresses contain colons (e.g., "[2001:db8::1]:8080"). Always use the standard library helpers:

// Splitting host and port:
host, portStr, err := net.SplitHostPort("[2001:db8::1]:8080")
// host = "2001:db8::1", portStr = "8080"

// Joining host and port:
addr := net.JoinHostPort("2001:db8::1", "8080")
// addr = "[2001:db8::1]:8080"

Chapter Summary

  • net.Listen and net.Dial are the entry points for server listeners and client connections.
  • net.Dialer and net.ListenConfig provide modern, context-aware connection establishment with timeouts.
  • net.Conn provides an idiomatic, stream-based interface satisfying io.Reader and io.Writer, safe for concurrent reads and writes.
  • *net.TCPConn exposes socket options like SetNoDelay, SetKeepAlive, and buffer sizing.
  • net.PacketConn / *net.UDPConn handle connectionless datagram communications using ReadFrom and WriteTo.
  • net.SplitHostPort and net.JoinHostPort must always be used to handle IPv4 and IPv6 strings safely.
  • In the next chapter, we will build complete, production-grade TCP servers and clients, exploring connection lifecycles, accept loops, and graceful shutdowns.