DEV Community

Rez Moss
Rez Moss

Posted on

3 1 1 1

Deep Dive into net/netip AddrPort Methods 6/7

Hey there! In our previous article, we explored Addr methods in detail. Now let's dive deep into AddrPort methods. AddrPort is a crucial type when working with network services since it combines an IP address with a port number. We'll explore every method with practical examples and real-world use cases.

Core Method Exploration

First, let's look at all the ways to work with AddrPort.

Creation and Parsing

package main

import (
    "fmt"
    "net/netip"
)

func demoAddrPortCreation() {
    // From string
    ap1, _ := netip.ParseAddrPort("192.168.1.1:8080")

    // From Addr and port
    addr := netip.MustParseAddr("192.168.1.1")
    ap2 := netip.AddrPortFrom(addr, 8080)

    // From raw IP and port
    ap3 := netip.AddrPortFrom(
        netip.AddrFrom4([4]byte{192, 168, 1, 1}),
        8080,
    )

    fmt.Printf("From string: %v\n", ap1)
    fmt.Printf("From components: %v\n", ap2)
    fmt.Printf("From raw: %v\n", ap3)
}
Enter fullscreen mode Exit fullscreen mode

Component Access Methods

func exploreComponents(ap netip.AddrPort) {
    // Get the address part
    addr := ap.Addr()
    fmt.Printf("Address: %v\n", addr)

    // Get the port number
    port := ap.Port()
    fmt.Printf("Port: %d\n", port)

    // Check validity
    fmt.Printf("Is valid: %v\n", ap.IsValid())

    // String representation
    str := ap.String()
    fmt.Printf("String form: %s\n", str)
}
Enter fullscreen mode Exit fullscreen mode

Practical Applications

1. Service Discovery System

Here's a robust service discovery implementation using AddrPort:

type ServiceType string

const (
    ServiceHTTP  ServiceType = "http"
    ServiceHTTPS ServiceType = "https"
    ServiceGRPC  ServiceType = "grpc"
)

type ServiceInstance struct {
    ID        string
    Type      ServiceType
    Endpoint  netip.AddrPort
    Metadata  map[string]string
    LastSeen  time.Time
}

type ServiceRegistry struct {
    services map[ServiceType]map[string]*ServiceInstance
    mu       sync.RWMutex
}

func NewServiceRegistry() *ServiceRegistry {
    return &ServiceRegistry{
        services: make(map[ServiceType]map[string]*ServiceInstance),
    }
}

func (sr *ServiceRegistry) Register(instance *ServiceInstance) error {
    sr.mu.Lock()
    defer sr.mu.Unlock()

    if !instance.Endpoint.IsValid() {
        return fmt.Errorf("invalid endpoint")
    }

    // Initialize type map if needed
    if sr.services[instance.Type] == nil {
        sr.services[instance.Type] = make(map[string]*ServiceInstance)
    }

    // Update or add instance
    instance.LastSeen = time.Now()
    sr.services[instance.Type][instance.ID] = instance

    return nil
}

func (sr *ServiceRegistry) Discover(stype ServiceType) []*ServiceInstance {
    sr.mu.RLock()
    defer sr.mu.RUnlock()

    var instances []*ServiceInstance
    for _, instance := range sr.services[stype] {
        instances = append(instances, instance)
    }

    return instances
}

func (sr *ServiceRegistry) Cleanup(maxAge time.Duration) {
    sr.mu.Lock()
    defer sr.mu.Unlock()

    now := time.Now()
    for stype, typeMap := range sr.services {
        for id, instance := range typeMap {
            if now.Sub(instance.LastSeen) > maxAge {
                delete(typeMap, id)
            }
        }

        if len(typeMap) == 0 {
            delete(sr.services, stype)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Connection Pool Manager

A connection pool that uses AddrPort for endpoint tracking:

type ConnState int

const (
    ConnIdle ConnState = iota
    ConnInUse
    ConnBroken
)

type PooledConn struct {
    Conn      net.Conn
    State     ConnState
    LastUsed  time.Time
    UseCount  int
}

type ConnectionPool struct {
    endpoints map[netip.AddrPort][]*PooledConn
    mu        sync.RWMutex
    maxIdle   time.Duration
    maxUses   int
}

func NewConnectionPool(maxIdle time.Duration, maxUses int) *ConnectionPool {
    return &ConnectionPool{
        endpoints: make(map[netip.AddrPort][]*PooledConn),
        maxIdle:   maxIdle,
        maxUses:   maxUses,
    }
}

func (cp *ConnectionPool) GetConnection(endpoint netip.AddrPort) (net.Conn, error) {
    cp.mu.Lock()
    defer cp.mu.Unlock()

    // Look for available connection
    conns := cp.endpoints[endpoint]
    for _, pc := range conns {
        if pc.State == ConnIdle {
            if time.Since(pc.LastUsed) > cp.maxIdle || pc.UseCount >= cp.maxUses {
                // Connection too old or overused - close and remove
                pc.Conn.Close()
                continue
            }

            pc.State = ConnInUse
            pc.LastUsed = time.Now()
            pc.UseCount++
            return pc.Conn, nil
        }
    }

    // Create new connection
    conn, err := net.Dial("tcp", endpoint.String())
    if err != nil {
        return nil, fmt.Errorf("failed to connect to %v: %w", endpoint, err)
    }

    pc := &PooledConn{
        Conn:     conn,
        State:    ConnInUse,
        LastUsed: time.Now(),
        UseCount: 1,
    }

    cp.endpoints[endpoint] = append(cp.endpoints[endpoint], pc)
    return conn, nil
}

func (cp *ConnectionPool) ReleaseConnection(endpoint netip.AddrPort, conn net.Conn) {
    cp.mu.Lock()
    defer cp.mu.Unlock()

    for _, pc := range cp.endpoints[endpoint] {
        if pc.Conn == conn {
            pc.State = ConnIdle
            pc.LastUsed = time.Now()
            return
        }
    }
}

func (cp *ConnectionPool) Cleanup() {
    cp.mu.Lock()
    defer cp.mu.Unlock()

    now := time.Now()
    for endpoint, conns := range cp.endpoints {
        var active []*PooledConn
        for _, pc := range conns {
            if pc.State == ConnIdle && 
               (now.Sub(pc.LastUsed) > cp.maxIdle || pc.UseCount >= cp.maxUses) {
                pc.Conn.Close()
                continue
            }
            active = append(active, pc)
        }

        if len(active) == 0 {
            delete(cp.endpoints, endpoint)
        } else {
            cp.endpoints[endpoint] = active
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Load Balancer Implementation

A load balancer using AddrPort for backend management:

type Backend struct {
    Endpoint     netip.AddrPort
    Weight       int
    Connections  int64
    LastChecked  time.Time
    Healthy      bool
}

type LoadBalancer struct {
    backends []*Backend
    mu       sync.RWMutex
}

func NewLoadBalancer() *LoadBalancer {
    return &LoadBalancer{}
}

func (lb *LoadBalancer) AddBackend(endpoint netip.AddrPort, weight int) error {
    if !endpoint.IsValid() {
        return fmt.Errorf("invalid endpoint")
    }

    lb.mu.Lock()
    defer lb.mu.Unlock()

    // Check for duplicate
    for _, b := range lb.backends {
        if b.Endpoint == endpoint {
            return fmt.Errorf("backend already exists")
        }
    }

    lb.backends = append(lb.backends, &Backend{
        Endpoint:    endpoint,
        Weight:      weight,
        LastChecked: time.Now(),
        Healthy:     true,
    })

    return nil
}

func (lb *LoadBalancer) RemoveBackend(endpoint netip.AddrPort) {
    lb.mu.Lock()
    defer lb.mu.Unlock()

    for i, b := range lb.backends {
        if b.Endpoint == endpoint {
            // Remove backend
            lb.backends = append(lb.backends[:i], lb.backends[i+1:]...)
            return
        }
    }
}

func (lb *LoadBalancer) GetBackend() (*Backend, error) {
    lb.mu.RLock()
    defer lb.mu.RUnlock()

    var totalWeight int
    var candidates []*Backend

    for _, b := range lb.backends {
        if b.Healthy {
            candidates = append(candidates, b)
            totalWeight += b.Weight
        }
    }

    if len(candidates) == 0 {
        return nil, fmt.Errorf("no healthy backends available")
    }

    // Weighted random selection
    target := rand.Intn(totalWeight)
    currentWeight := 0

    for _, b := range candidates {
        currentWeight += b.Weight
        if target < currentWeight {
            atomic.AddInt64(&b.Connections, 1)
            return b, nil
        }
    }

    // Shouldn't reach here, but just in case
    b := candidates[len(candidates)-1]
    atomic.AddInt64(&b.Connections, 1)
    return b, nil
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Validation Always validate AddrPort before use:
   func validateEndpoint(ep netip.AddrPort) error {
       if !ep.IsValid() {
           return fmt.Errorf("invalid endpoint")
       }

       if ep.Addr().IsUnspecified() {
           return fmt.Errorf("unspecified address not allowed")
       }

       return nil
   }
Enter fullscreen mode Exit fullscreen mode
  1. String Handling Be careful with string conversions:
   func formatEndpoint(ep netip.AddrPort) string {
       if ep.Addr().Is6() {
           // IPv6 addresses need brackets
           return fmt.Sprintf("[%s]:%d", ep.Addr(), ep.Port())
       }
       return ep.String()
   }
Enter fullscreen mode Exit fullscreen mode
  1. Port Range Validation Check port numbers when needed:
   func isUserPort(ep netip.AddrPort) bool {
       port := ep.Port()
       return port >= 1024 && port <= 49151
   }
Enter fullscreen mode Exit fullscreen mode

Common Patterns

  1. Service Configuration
   type ServiceConfig struct {
       Listen    netip.AddrPort
       Upstream  []netip.AddrPort
   }

   func parseConfig(config map[string]string) (ServiceConfig, error) {
       var cfg ServiceConfig

       if listen, err := netip.ParseAddrPort(config["listen"]); err != nil {
           return cfg, fmt.Errorf("invalid listen address: %w", err)
       } else {
           cfg.Listen = listen
       }

       for _, up := range strings.Split(config["upstream"], ",") {
           if endpoint, err := netip.ParseAddrPort(up); err != nil {
               return cfg, fmt.Errorf("invalid upstream %q: %w", up, err)
           } else {
               cfg.Upstream = append(cfg.Upstream, endpoint)
           }
       }

       return cfg, nil
   }
Enter fullscreen mode Exit fullscreen mode
  1. Address Family Handling
   func getDialNetwork(ep netip.AddrPort) string {
       if ep.Addr().Is6() {
           return "tcp6"
       }
       return "tcp4"
   }
Enter fullscreen mode Exit fullscreen mode

Performance Tips

  1. Avoid String Parsing
   // Bad
   ep, _ := netip.ParseAddrPort(fmt.Sprintf("%s:%d", addr, port))

   // Good
   ep := netip.AddrPortFrom(addr, port)
Enter fullscreen mode Exit fullscreen mode
  1. Efficient Storage
   // Bad
   map[string]string  // Using string representations

   // Good
   map[netip.AddrPort]interface{}  // Using AddrPort directly
Enter fullscreen mode Exit fullscreen mode
  1. Batch Operations
   func batchConnect(endpoints []netip.AddrPort) []net.Conn {
       var wg sync.WaitGroup
       conns := make([]net.Conn, len(endpoints))

       for i, ep := range endpoints {
           wg.Add(1)
           go func(i int, ep netip.AddrPort) {
               defer wg.Done()
               if conn, err := net.Dial("tcp", ep.String()); err == nil {
                   conns[i] = conn
               }
           }(i, ep)
       }

       wg.Wait()
       return conns
   }
Enter fullscreen mode Exit fullscreen mode

What's Next?

In our next article, we'll explore Prefix methods in depth, completing our detailed examination of the core types in net/netip. We'll see how to work effectively with CIDR notations and subnet operations.

Until then, keep experimenting with AddrPort! It's a fundamental building block for network services in Go.

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs