DEV Community

Rez Moss
Rez Moss

Posted on

2

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.

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

👋 Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay