Sulabh Biswas / Blog

Deep dives into Linux networking and Go

Go Networking Tips and Tricks

Go’s built-in networking capabilities are powerful, but there are several techniques you can use to build even more performant and reliable network services.

1. Connection Pooling

Always reuse connections when possible:

type ConnectionPool struct {
    pool chan *net.Conn
    factory func() (*net.Conn, error)
}

func (p *ConnectionPool) Get() (*net.Conn, error) {
    select {
    case conn := <-p.pool:
        return conn, nil
    default:
        return p.factory()
    }
}

func (p *ConnectionPool) Put(conn *net.Conn) {
    select {
    case p.pool <- conn:
    default:
        conn.Close()
    }
}

2. Efficient Buffer Management

Use sync.Pool to reuse buffers:

var bufferPool = sync.Pool{
    New: func() interface{} {
        return make([]byte, 4096)
    },
}

func handleConnection(conn net.Conn) {
    defer conn.Close()
    
    buf := bufferPool.Get().([]byte)
    defer bufferPool.Put(buf)
    
    for {
        n, err := conn.Read(buf)
        if err != nil {
            break
        }
        // Process data...
    }
}

3. Context-aware Timeouts

Always use context for cancellation and timeouts:

func dialWithTimeout(ctx context.Context, network, address string) (net.Conn, error) {
    dialer := &net.Dialer{
        Timeout: 30 * time.Second,
    }
    
    return dialer.DialContext(ctx, network, address)
}

4. TCP Optimizations

Fine-tune TCP parameters for better performance:

func optimizeTCP(conn *net.TCPConn) error {
    // Enable TCP_NODELAY
    if err := conn.SetNoDelay(true); err != nil {
        return err
    }
    
    // Set keepalive
    if err := conn.SetKeepAlive(true); err != nil {
        return err
    }
    
    // Set keepalive period
    if tcpConn, err := conn.SyscallConn(); err == nil {
        tcpConn.Control(func(fd uintptr) {
            syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, 
                syscall.TCP_KEEPINTVL, 15)
            syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, 
                syscall.TCP_KEEPCNT, 3)
        })
    }
    
    return nil
}

5. Graceful Shutdown

Implement proper shutdown handling:

type Server struct {
    httpServer *http.Server
    wg         sync.WaitGroup
}

func (s *Server) Start(addr string) error {
    s.httpServer = &http.Server{Addr: addr}
    
    s.wg.Add(1)
    go func() {
        defer s.wg.Done()
        if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Printf("Server error: %v", err)
        }
    }()
    
    return nil
}

func (s *Server) Shutdown(ctx context.Context) error {
    if err := s.httpServer.Shutdown(ctx); err != nil {
        return err
    }
    
    s.wg.Wait()
    return nil
}

6. Monitoring and Metrics

Add proper observability:

import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
)

var (
    connectionsTotal = promauto.NewCounter(prometheus.CounterOpts{
        Name: "network_connections_total",
        Help: "Total number of network connections",
    })
    
    connectionDuration = promauto.NewHistogram(prometheus.HistogramOpts{
        Name: "network_connection_duration_seconds",
        Help: "Duration of network connections",
    })
)

Conclusion

These tips will help you build more robust and performant network services in Go. Remember to always profile your applications to identify bottlenecks and optimize based on real-world usage patterns.