udp

package
v1.22.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 17, 2026 License: MIT Imports: 12 Imported by: 0

README

UDP Server

Go Version License Coverage

Lightweight, high-performance UDP server implementation with atomic state management, graceful shutdown, lifecycle callbacks, and comprehensive monitoring capabilities.


Table of Contents


Overview

The udp package provides a production-ready UDP server optimized for connectionless datagram processing. It implements an atomic state management model suitable for stateless request/response patterns and event streaming use cases.

Design Philosophy
  1. Connectionless First: Optimized for stateless UDP datagram processing
  2. Lock-Free Operations: Atomic operations for zero-contention state management
  3. Simplicity: Minimal API surface with clear semantics
  4. Observable: Real-time monitoring via callbacks and state methods
  5. Context-Aware: Full integration with Go's context for lifecycle control
Key Features
  • UDP Server: Pure UDP datagram processing (connectionless)
  • Atomic State: Lock-free state management with atomic operations
  • Graceful Shutdown: Context-based shutdown with configurable timeouts
  • Lifecycle Callbacks: Hook into server events (error, info, connection updates)
  • Thread-Safe: All operations safe for concurrent use
  • Context Integration: Full context support for cancellation and deadlines
  • Zero Connection Tracking: Stateless design (OpenConnections always returns 0)
  • TLS N/A: UDP is connectionless; no TLS at transport layer
  • Zero Dependencies: Only standard library + golib packages

Architecture

Component Diagram
┌────────────────────────────────────────────────────────┐
│                   UDP Server                           │
├────────────────────────────────────────────────────────┤
│                                                        │
│  ┌────────────────┐       ┌───────────────────┐        │
│  │    Listener    │       │  Context Manager  │        │
│  │  (net.UDPConn) │       │  (cancellation)   │        │
│  └────────┬───────┘       └─────────┬─────────┘        │
│           │                         │                  │
│           ▼                         ▼                  │
│  ┌──────────────────────────────────────────┐          │
│  │       Datagram Read Loop                 │          │
│  │     (single goroutine per server)        │          │
│  └──────────────┬───────────────────────────┘          │
│                 │                                      │
│                 ▼                                      │
│         Per-Datagram Handler                           │
│         ┌──────────────────────┐                       │
│         │  sCtx (I/O wrapper)  │                       │
│         │   - Read (from buf)  │                       │
│         │   - Write (no-op)    │                       │
│         │   - Remote/Local     │                       │
│         └──────────┬───────────┘                       │
│                    │                                   │
│                    ▼                                   │
│         ┌──────────────────────┐                       │
│         │   User Handler       │                       │
│         │  (processes dgram)   │                       │
│         └──────────────────────┘                       │
│                                                        │
│  ┌──────────────────────────────────────────┐          │
│  │         Atomic State (libatm.Value)      │          │
│  │   - run: server running                  │          │
│  │   - gon: server terminated               │          │
│  │   - ad:  server address                  │          │
│  │   - fe:  error callback                  │          │
│  │   - fi:  info callback                   │          │
│  │   - fs:  server info callback            │          │
│  └──────────────────────────────────────────┘          │
│                                                        │
└────────────────────────────────────────────────────────┘
Data Flow
Client                     UDP Server                Handler
  │                             │                       │
  │───── Datagram ─────────────▶│                       │
  │                             │                       │
  │                             │──── sCtx ────────────▶│
  │                             │                       │
  │                             │                  Process
  │                             │                       │
  │                             │◀───── Close ──────────│
  │                             │                       │
  │◀──── Response ──────────────│                       │
  │    (via client socket)      │                       │

Key Points:

  • Stateless: Each datagram is independent
  • No Connection Tracking: UDP is connectionless by nature
  • Handler Per Datagram: Each datagram spawns a goroutine
  • Context Per Datagram: sCtx wraps datagram buffer and remote address
State Management

The server uses atomic operations for lock-free state management:

type srv struct {
    run atomic.Bool          // Server running state
    gon atomic.Bool          // Server gone (terminated)
    ad  libatm.Value[string] // Listen address
    fe  libatm.Value[FuncError]     // Error callback
    fi  libatm.Value[FuncInfo]      // Info callback
    fs  libatm.Value[FuncInfoSrv]   // Server info callback
}

State Transitions:

  1. New()gon=true, run=false (created but not started)
  2. Listen()gon=false, run=true (active)
  3. Shutdown()/Close()gon=true, run=false (terminated)

Performance

Throughput
Metric Value Conditions
Datagram Processing ~50,000 dgrams/sec Single server, 1KB datagrams
Handler Spawn <100 µs/datagram Goroutine creation overhead
State Query <10 ns Atomic read operations
Shutdown Latency <50 ms Graceful shutdown time
Memory Usage
Component Memory Notes
Base Server ~500 bytes Struct + atomics
Per Datagram ~4 KB sCtx + buffer (configurable)
UDP Buffer 65,507 bytes Max UDP datagram size
Goroutine Stack 2-8 KB Per handler goroutine

Total per active datagram: ~12 KB

Scalability
  • Datagrams: Unlimited (stateless)
  • Concurrent Handlers: Limited by OS (typically 10,000+)
  • Listeners: 1 per server instance
  • CPU Cores: Scales linearly with available cores

Recommended:

  • Use UDP for stateless, high-throughput scenarios
  • Keep handler processing time minimal (<1ms)
  • Consider datagram size vs. MTU (~1,500 bytes for Ethernet)

Use Cases

1. Syslog Server

Receive log messages over UDP from distributed systems.

handler := func(ctx libsck.Context) {
    defer ctx.Close()
    buf := make([]byte, 4096)
    n, _ := ctx.Read(buf)
    processSyslogMessage(buf[:n])
}
2. Metrics Collector (StatsD)

High-throughput metric ingestion with stateless processing.

handler := func(ctx libsck.Context) {
    defer ctx.Close()
    buf := make([]byte, 1024)
    n, _ := ctx.Read(buf)
    parseAndStoreMetric(buf[:n])
}
3. DNS Server

Stateless query/response over UDP.

handler := func(ctx libsck.Context) {
    defer ctx.Close()
    buf := make([]byte, 512)
    n, _ := ctx.Read(buf)
    response := resolveDNSQuery(buf[:n])
    // Send response via separate client socket
}
4. Game Server (Real-time)

Low-latency state updates for multiplayer games.

handler := func(ctx libsck.Context) {
    defer ctx.Close()
    buf := make([]byte, 2048)
    n, _ := ctx.Read(buf)
    processGameState(buf[:n], ctx.RemoteHost())
}
5. IoT Data Ingestion

High-volume sensor data from embedded devices.

handler := func(ctx libsck.Context) {
    defer ctx.Close()
    buf := make([]byte, 256)
    n, _ := ctx.Read(buf)
    storeSensorData(buf[:n])
}

Quick Start

Installation
go get github.com/nabbar/golib/socket/server/udp
Basic Echo Server
package main

import (
    "context"
    "fmt"
    "log"
    
    libsck "github.com/nabbar/golib/socket"
    sckcfg "github.com/nabbar/golib/socket/config"
    "github.com/nabbar/golib/socket/server/udp"
)

func main() {
    // Create server configuration
    cfg := sckcfg.Server{
        Network: "udp",
        Address: ":8080",
    }
    
    // Define handler
    handler := func(ctx libsck.Context) {
        defer ctx.Close()
        
        buf := make([]byte, 1024)
        n, err := ctx.Read(buf)
        if err != nil {
            log.Printf("Read error: %v", err)
            return
        }
        
        fmt.Printf("Received: %s from %s\n", buf[:n], ctx.RemoteHost())
    }
    
    // Create server
    srv, err := udp.New(nil, handler, cfg)
    if err != nil {
        log.Fatal(err)
    }
    
    // Listen
    ctx := context.Background()
    if err := srv.Listen(ctx); err != nil {
        log.Fatal(err)
    }
}
Server with Callbacks
// Error callback
srv.RegisterFuncError(func(err error) {
    log.Printf("[ERROR] %v", err)
})

// Info callback (connection events)
srv.RegisterFuncInfo(func(local, remote string, state libsck.ConnState) {
    log.Printf("[INFO] %s -> %s: %s", remote, local, state)
})

// Server info callback (lifecycle events)
srv.RegisterFuncInfoSrv(func(local string, state libsck.ConnState) {
    log.Printf("[SERVER] %s: %s", local, state)
})
Production Server
// Production setup with graceful shutdown
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// Register callbacks
srv.RegisterFuncError(logError)
srv.RegisterFuncInfo(logConnection)

// Start server in goroutine
go func() {
    if err := srv.Listen(ctx); err != nil {
        log.Printf("Listen error: %v", err)
    }
}()

// Wait for running state
time.Sleep(100 * time.Millisecond)
if !srv.IsRunning() {
    log.Fatal("Server failed to start")
}

// Graceful shutdown on signal
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
<-sigChan

shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer shutdownCancel()

if err := srv.Shutdown(shutdownCtx); err != nil {
    log.Printf("Shutdown error: %v", err)
}

Best Practices

Handler Design
  1. Keep Handlers Fast: Process datagrams in <1ms when possible
  2. Always Close Context: Use defer ctx.Close() at handler start
  3. Handle Read Errors: UDP reads can fail, check errors
  4. Avoid Blocking: Don't block in handlers (spawn goroutines if needed)
// ✅ Good: Fast, non-blocking
handler := func(ctx libsck.Context) {
    defer ctx.Close()
    buf := make([]byte, 1024)
    n, err := ctx.Read(buf)
    if err != nil {
        return
    }
    processQuickly(buf[:n])
}

// ❌ Bad: Slow, blocking
handler := func(ctx libsck.Context) {
    defer ctx.Close()
    buf := make([]byte, 1024)
    n, _ := ctx.Read(buf)
    time.Sleep(1 * time.Second) // Blocks goroutine
    database.Query(buf[:n])     // Slow I/O
}
Buffer Sizing
  1. MTU Awareness: Keep buffers ≤1,500 bytes for Ethernet
  2. Max UDP Size: Maximum UDP datagram is 65,507 bytes
  3. Typical Sizes: 512-2,048 bytes for most use cases
// For DNS queries
buf := make([]byte, 512)

// For general use
buf := make([]byte, 1500)

// For jumbo frames
buf := make([]byte, 9000)
Error Handling
  1. Register Error Callback: Always log errors
  2. Handle Context Errors: Check for cancellation
  3. Graceful Degradation: Don't crash on malformed datagrams
srv.RegisterFuncError(func(err error) {
    log.Printf("[UDP ERROR] %v", err)
})

handler := func(ctx libsck.Context) {
    defer ctx.Close()
    
    select {
    case <-ctx.Done():
        return // Context cancelled
    default:
    }
    
    buf := make([]byte, 1024)
    n, err := ctx.Read(buf)
    if err != nil {
        return // Log via error callback
    }
    
    // Process...
}
Lifecycle Management
  1. Context for Shutdown: Use context cancellation
  2. Check IsRunning: Verify server state
  3. Graceful Shutdown: Use Shutdown() with timeout
// Start
go srv.Listen(ctx)
time.Sleep(100 * time.Millisecond)
if !srv.IsRunning() {
    log.Fatal("Failed to start")
}

// Shutdown
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
    log.Printf("Shutdown timeout: %v", err)
}
Testing

See TESTING.md for comprehensive testing guidelines.


API Reference

ServerUdp Interface
type ServerUdp interface {
    libsck.Server // Extends base Server interface
}

Extends github.com/nabbar/golib/socket.Server:

type Server interface {
    // Lifecycle
    Listen(ctx context.Context) error
    Shutdown(ctx context.Context) error
    Close() error
    
    // State
    IsRunning() bool
    IsGone() bool
    OpenConnections() int64 // Always returns 0 for UDP
    
    // Configuration
    RegisterServer(address string) error
    SetTLS(enable bool, tlsConfig libtls.TLSConfig) // No-op for UDP
    
    // Callbacks
    RegisterFuncError(fct FuncError)
    RegisterFuncInfo(fct FuncInfo)
    RegisterFuncInfoSrv(fct FuncInfoSrv)
}
Configuration
Server Config
type Server struct {
    Network string           // Must be "udp", "udp4", or "udp6"
    Address string           // Listen address (e.g., ":8080", "0.0.0.0:9000")
    
    // UDP-specific (unused but part of base config)
    PermFile       os.FileMode  // N/A for UDP
    GroupPerm      int32        // N/A for UDP
    ConIdleTimeout time.Duration // N/A for UDP (connectionless)
    TLS            struct{...}  // N/A for UDP (no TLS at transport layer)
}
Constructor
func New(
    updateConn libsck.UpdateConn, // Optional: Called when listener is created (can be nil)
    handler libsck.HandlerFunc,   // Required: Datagram handler
    cfg sckcfg.Server,            // Required: Server configuration
) (ServerUdp, error)
Error Codes
var (
    ErrInvalidAddress   = errors.New("invalid address")
    ErrInvalidHandler   = errors.New("invalid handler")
    ErrShutdownTimeout  = errors.New("shutdown timeout")
    ErrInvalidInstance  = errors.New("invalid instance")
)

Contributing

Contributions are welcome! Please follow these guidelines:

  1. Fork & Branch: Create a feature branch from main
  2. Test Coverage: Maintain >70% coverage
  3. Race Detector: All tests must pass with -race
  4. Documentation: Update docs for API changes
  5. BDD Style: Use Ginkgo/Gomega for tests

See CONTRIBUTING.md for details.


Limitations

By Design
  1. No Connection Tracking: OpenConnections() always returns 0 (UDP is stateless)
  2. No TLS Support: TLS requires connection-oriented protocol (use DTLS separately)
  3. No Idle Timeout: UDP has no persistent connections to timeout
  4. Write is No-Op: Context.Write() returns io.ErrClosedPipe (response via client socket)
UDP Protocol Limits
  1. Datagram Size: Maximum 65,507 bytes (65,535 - 8-byte header - 20-byte IP header)
  2. No Ordering: Datagrams may arrive out of order
  3. No Reliability: Datagrams may be lost or duplicated
  4. No Flow Control: No backpressure mechanism
Implementation Constraints
  1. Single Listener: One net.UDPConn per server
  2. Goroutine Per Datagram: Can exhaust goroutines under extreme load
  3. No Connection Pooling: Each datagram is independent
  4. Context Deadline: Inherited from Listen() context, not per-datagram

Resources


AI Transparency

In compliance with EU AI Act Article 50.4: AI assistance was used for documentation generation, test creation, and code review under human supervision. All core functionality is human-designed and validated.


License

License: MIT License - See LICENSE file for details
Maintained By: Nicolas JUHEL
Package: github.com/nabbar/golib/socket/server/udp

Documentation

Overview

Package udp provides a high-performance, stateless UDP server implementation designed for robustness and low-latency datagram processing.

1. ARCHITECTURE

This package implements a production-grade UDP server that minimizes the overhead of connection management. It is designed to scale horizontally by leveraging asynchronous datagram processing.

┌─────────────────────────────────────────────────────────────┐
│                    UDP SERVER ARCHITECTURE                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   [ NETWORK INTERFACE ]     [ KERNEL SPACE ]      [ USER SPACE ]
│          │                         │                    │
│   UDP Datagram 1 --------> [ Receive Buffer ] <--- [ Read() ]
│   UDP Datagram 2 --------> [ Receive Buffer ] <--- [ Read() ]
│          │                         │                    │
│          v                         v                    v
│   [ EVENT DISPATCH ]        [ CONTEXT WRAPPER ]   [ USER HANDLER ]
│          │                         │                    │
│   ┌──────────────┐          ┌────────────────┐    ┌───────────────┐
│   │  Done()      │ <─────── │ context.Context│ <──│ HandlerFunc   │
│   │  (Cancel)    │          └────────────────┘    │ (Goroutine)   │
│   └──────────────┘                                └───────┬───────┘
│          ^                                                │
│          │          ┌─────────────────────────────────────┘
│   ┌──────────────┐  │       ┌───────────────────┐
│   │ Gone Channel │ ─┴─────> │  Shutdown Signal  │
│   │ (gnc Broadcast)         │  (Instant Exit)   │
│   └──────────────┘          └───────────────────┘
└─────────────────────────────────────────────────────────────┘

2. KEY FEATURES & OPTIMIZATIONS

  • Stateless Operation: Unlike TCP, the UDP server maintains no session state by default. This allows the server to handle millions of datagrams with near-zero memory footprint for per-client management.

  • Event-Driven Shutdown (Gone Channel): Traditional UDP servers often rely on periodic polling to check for shutdown. This package uses the "gnc" broadcast channel. When the server is closed, the 'gnc' channel is closed instantly, notifying the main listener loop to exit without any wait.

  • Atomic State Control: All lifecycle flags (IsRunning, IsGone) are managed via atomic operations (sync/atomic), ensuring lock-free thread safety across monitoring goroutines.

  • Hook-Based Tuning: The UpdateConn callback provides a hook to call SetReadBuffer and SetWriteBuffer directly on the underlying net.UDPConn, which is critical for preventing packet drops under high-bandwidth loads.

3. DATA FLOW

The following diagram illustrates the flow of a datagram through the server:

[PEER]            [KERNEL BUFFER]            [SERVER LOOP]           [HANDLER]
   │                     │                         │                     │
   │──(UDP Datagram)────>│                         │                     │
   │                     │                         │                     │
   │                     │<────(Blocking Read)─────│                     │
   │                     │                         │                     │
   │                     │────────(Payload)───────>│                     │
   │                     │                         │                     │
   │                     │                         │────────(Data)──────>│
   │                     │                         │                     │
   │                     │                         │<──────(Processing)──│
   │                     │                         │                     │
   │<───(UDP Response)───┼─────────────────────────┼────────(WriteTo)────│
   │                     │                         │                     │

4. UDP HANDLING SEMANTICS & CAVEATS (RFC 768) RFC 768)" aria-label="Go to 4. UDP HANDLING SEMANTICS & CAVEATS (RFC 768)">¶

UDP is inherently connectionless, which has specific implications for this server:

  1. Shared Socket: There is only one listener socket for all incoming data. This means only one HandlerFunc is spawned per Listen() call. The handler is responsible for managing its own concurrency if needed.

  2. Reliability (RFC 768): This package does NOT implement retries, ACKs, or message ordering. If your application requires these, you must implement them within your HandlerFunc or use a protocol like TCP.

  3. Max Payload: Datagrams exceeding the MTU (typically 1500 bytes) may be fragmented by the network stack. It's recommended to keep datagram sizes under 1472 bytes for IPv4 or 1280 bytes for IPv6 for maximum reliability.

5. BEST PRACTICES & PERFORMANCE TUNING

  • High-Throughput Tuning: Always increase kernel buffers for high-load UDP servers to prevent "ICMP Destination Unreachable" or packet drops:

    updateFn := func(conn net.Conn) { if udp, ok := conn.(*net.UDPConn); ok { _ = udp.SetReadBuffer(2 * 1024 * 1024) // 2MB Read Buffer _ = udp.SetWriteBuffer(2 * 1024 * 1024) // 2MB Write Buffer } }

  • Buffer Management: To minimize Garbage Collector (GC) pressure, use a sync.Pool for the buffers used within the handler:

    var bufPool = sync.Pool{ New: func() any { return make([]byte, 65535) }, } // Inside handler... buf := bufPool.Get().([]byte) defer bufPool.Put(buf) n, remoteAddr, _ := ctx.Read(buf)

6. USE CASES

  • Case 1: High-Performance Metrics Gathering (StatsD-like systems).
  • Case 2: Real-time Streaming (Audio/Video datagrams).
  • Case 3: Network Probing & Monitoring Tools.
  • Case 4: DNS-like request/response protocols.

7. CONCRETE IMPLEMENTATION EXAMPLE

package main

import (
    "context"
    "log"
    "github.com/nabbar/golib/socket"
    "github.com/nabbar/golib/socket/config"
    "github.com/nabbar/golib/socket/server/udp"
)

func main() {
    // 1. Define the handler (spawns once)
    handler := func(ctx socket.Context) {
        buf := make([]byte, 65535)
        for {
            n, err := ctx.Read(buf)
            if err != nil {
                return
            }
            log.Printf("Received %d bytes: %s", n, string(buf[:n]))
        }
    }

    // 2. Setup Config
    cfg := config.Server{
        Network: "udp",
        Address: ":1234",
    }

    // 3. Instantiate and Start
    srv, _ := udp.New(nil, handler, cfg)
    srv.Listen(context.Background())
}

Package udp provides a high-performance, stateless UDP server implementation.

Example (BasicServer)

Basic Echo Server Example

This example demonstrates the simplest possible setup for a UDP server. The handler reads exactly one datagram and prints it to the console.

Logic Flow

  • Configure the server on a dynamic port (":0").
  • Define a handler function.
  • Start the server using a cancellable context.
package main

import (
	"context"
	"fmt"
	"io"
	"time"

	libptc "github.com/nabbar/golib/network/protocol"
	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"
	"github.com/nabbar/golib/socket/server/udp"
)

func main() {
	// Create server configuration
	cfg := sckcfg.Server{
		Network: libptc.NetworkUDP,
		Address: ":0", // Let OS choose available port
	}

	// Define simple handler
	handler := func(ctx libsck.Context) {
		defer ctx.Close()

		buf := make([]byte, 1024)
		n, err := ctx.Read(buf)
		if err != nil && err != io.EOF {
			return
		}

		fmt.Printf("Received: %s\n", buf[:n])
	}

	// Create server
	srv, err := udp.New(nil, handler, cfg)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Start server (would block in real usage)
	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
	defer cancel()

	go func() {
		_ = srv.Listen(ctx)
	}()

	fmt.Println("UDP server created successfully")
}
Output:
UDP server created successfully
Example (ConfigFromStruct)

Example_configFromStruct demonstrates using config.Server struct.

This example shows the recommended way to create a server using the config package for type-safe configuration.

package main

import (
	"fmt"

	libptc "github.com/nabbar/golib/network/protocol"
	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"
	"github.com/nabbar/golib/socket/server/udp"
)

func main() {
	// Create configuration
	cfg := sckcfg.Server{
		Network: libptc.NetworkUDP,
		Address: ":9008",
	}

	// Validate configuration
	if err := cfg.Validate(); err != nil {
		fmt.Printf("Invalid configuration: %v\n", err)
		return
	}

	// Define handler
	handler := func(ctx libsck.Context) {
		buf := make([]byte, 1024)
		_, _ = ctx.Read(buf)
	}

	// Create server
	srv, err := udp.New(nil, handler, cfg)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Server configured for %s on %s\n",
		cfg.Network.String(), cfg.Address)
	_ = srv
}
Output:
Server configured for udp on :9008
Example (EchoServer)

Example_echoServer demonstrates a complete UDP echo server.

This example shows a server that echoes back received datagrams to the sender using ReadFrom/WriteTo pattern.

package main

import (
	"fmt"
	"io"
	"log"

	libptc "github.com/nabbar/golib/network/protocol"
	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"
	"github.com/nabbar/golib/socket/server/udp"
)

func main() {
	cfg := sckcfg.Server{
		Network: libptc.NetworkUDP,
		Address: ":9001",
	}

	// Echo handler using ReadFrom/WriteTo
	handler := func(ctx libsck.Context) {
		// Type assertion to get UDP connection
		type udpContext interface {
			libsck.Context
			// Would need access to underlying connection for WriteTo
		}

		// For demonstration, just read
		buf := make([]byte, 65507) // Max UDP datagram
		for {
			n, err := ctx.Read(buf)
			if err != nil {
				if err == io.EOF || err == io.ErrClosedPipe {
					return
				}
				log.Printf("Read error: %v", err)
				return
			}

			// In real implementation, would echo back using WriteTo
			_ = buf[:n]
		}
	}

	srv, err := udp.New(nil, handler, cfg)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Echo server configured on %s\n", cfg.Address)
	_ = srv
}
Output:
Echo server configured on :9001
Example (ErrorHandling)

Example_errorHandling demonstrates comprehensive error handling.

This example shows how to handle various error scenarios including invalid configuration, connection errors, and I/O errors.

package main

import (
	"fmt"

	libptc "github.com/nabbar/golib/network/protocol"
	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"
	"github.com/nabbar/golib/socket/server/udp"
)

func main() {
	// Attempt to create server with invalid configuration
	invalidCfg := sckcfg.Server{
		Network: libptc.NetworkUDP,
		Address: "", // Invalid: empty address
	}

	handler := func(ctx libsck.Context) {}

	_, err := udp.New(nil, handler, invalidCfg)
	if err != nil {
		fmt.Printf("Configuration error detected: %v\n", err)
		return
	}

	fmt.Println("This should not print")
}
Output:
Configuration error detected: invalid listen address
Example (GracefulShutdown)

Example_gracefulShutdown demonstrates proper server shutdown.

This example shows how to handle graceful shutdown using context cancellation and the Shutdown() method.

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	libptc "github.com/nabbar/golib/network/protocol"
	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"
	"github.com/nabbar/golib/socket/server/udp"
)

func main() {
	cfg := sckcfg.Server{
		Network: libptc.NetworkUDP,
		Address: ":9004",
	}

	handler := func(ctx libsck.Context) {
		buf := make([]byte, 1024)
		for {
			_, err := ctx.Read(buf)
			if err != nil {
				return // Exit on error
			}
		}
	}

	srv, err := udp.New(nil, handler, cfg)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Start server with cancellable context
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Start server in goroutine
	go func() {
		if err := srv.Listen(ctx); err != nil {
			if err != context.Canceled {
				log.Printf("Listen error: %v", err)
			}
		}
	}()

	// Wait for server to start
	time.Sleep(10 * time.Millisecond)

	// Trigger shutdown
	cancel()

	// Wait with timeout
	shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer shutdownCancel()

	if err := srv.Shutdown(shutdownCtx); err != nil {
		fmt.Printf("Shutdown error: %v\n", err)
		return
	}

	fmt.Println("Server shut down gracefully")
}
Output:
Server shut down gracefully
Example (IntegrationTest)

Example_integrationTest demonstrates testing UDP servers.

This example shows how to write integration tests for UDP servers by creating a server and client for testing.

package main

import (
	"context"
	"fmt"
	"time"

	libptc "github.com/nabbar/golib/network/protocol"
	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"
	"github.com/nabbar/golib/socket/server/udp"
)

func main() {
	cfg := sckcfg.Server{
		Network: libptc.NetworkUDP,
		Address: "127.0.0.1:0", // Bind to loopback, random port
	}

	receivedData := make(chan []byte, 1)

	handler := func(ctx libsck.Context) {
		buf := make([]byte, 1024)
		n, err := ctx.Read(buf)
		if err != nil {
			return
		}
		receivedData <- buf[:n]
	}

	srv, err := udp.New(nil, handler, cfg)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go func() {
		_ = srv.Listen(ctx)
	}()

	time.Sleep(10 * time.Millisecond)

	fmt.Println("Integration test server ready")
	// In real test, would send test datagram and verify
	
Example (MulticastServer)

Example_multicastServer demonstrates handling multicast UDP traffic.

This example shows configuration for a server that can receive multicast datagrams (concept demonstration).

package main

import (
	"fmt"
	"net"

	libptc "github.com/nabbar/golib/network/protocol"
	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"
	"github.com/nabbar/golib/socket/server/udp"
)

func main() {
	cfg := sckcfg.Server{
		Network: libptc.NetworkUDP,
		Address: ":9005",
	}

	// Configure multicast options
	updateConn := func(conn net.Conn) {
		if udpConn, ok := conn.(*net.UDPConn); ok {
			// In real implementation, would join multicast group
			// using udpConn.SetMulticastInterface() and related methods
			_ = udpConn
			fmt.Println("Multicast options configured")
		}
	}

	handler := func(ctx libsck.Context) {
		buf := make([]byte, 1024)
		_, _ = ctx.Read(buf)
	}

	srv, err := udp.New(updateConn, handler, cfg)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Println("Multicast server configured")
	_ = srv
}
Output:
Multicast server configured
Example (ServerWithCallbacks)

Server with Monitoring Callbacks

This example shows how to register observability hooks for monitoring. These callbacks are executed asynchronously and do not block the datagram flow.

package main

import (
	"fmt"
	"net"

	libptc "github.com/nabbar/golib/network/protocol"
	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"
	"github.com/nabbar/golib/socket/server/udp"
)

func main() {
	cfg := sckcfg.Server{
		Network: libptc.NetworkUDP,
		Address: ":9002",
	}

	handler := func(ctx libsck.Context) {
		// Handler implementation
		buf := make([]byte, 1024)
		_, _ = ctx.Read(buf)
	}

	srv, err := udp.New(nil, handler, cfg)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Register error callback
	srv.RegisterFuncError(func(errs ...error) {
		for _, e := range errs {
			if e != nil {
				fmt.Printf("Server error: %v\n", e)
			}
		}
	})

	// Register connection info callback
	srv.RegisterFuncInfo(func(local, remote net.Addr, state libsck.ConnState) {
		fmt.Printf("Connection event: %s (local: %v, remote: %v)\n",
			state.String(), local, remote)
	})

	// Register server info callback
	srv.RegisterFuncInfoServer(func(msg string) {
		fmt.Printf("Server info: %s\n", msg)
	})

	fmt.Println("Callbacks registered successfully")
}
Output:
Callbacks registered successfully
Example (SocketConfiguration)

Example_socketConfiguration demonstrates custom socket options.

This example shows how to use UpdateConn callback to configure socket buffer sizes and other options.

package main

import (
	"fmt"
	"net"

	libptc "github.com/nabbar/golib/network/protocol"
	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"
	"github.com/nabbar/golib/socket/server/udp"
)

func main() {
	cfg := sckcfg.Server{
		Network: libptc.NetworkUDP,
		Address: ":9003",
	}

	// Configure socket with custom options
	updateConn := func(conn net.Conn) {
		if udpConn, ok := conn.(*net.UDPConn); ok {
			// Set large read buffer for high throughput
			_ = udpConn.SetReadBuffer(1024 * 1024) // 1MB

			// Set large write buffer
			_ = udpConn.SetWriteBuffer(1024 * 1024) // 1MB

			fmt.Println("Socket buffers configured")
		}
	}

	handler := func(ctx libsck.Context) {
		buf := make([]byte, 1024)
		_, _ = ctx.Read(buf)
	}

	srv, err := udp.New(updateConn, handler, cfg)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Println("Server created with custom socket configuration")
	_ = srv
}
Output:
Server created with custom socket configuration
Example (StateMonitoring)

Example_stateMonitoring demonstrates server state monitoring.

This example shows how to monitor server state using IsRunning(), IsGone(), and OpenConnections() methods.

package main

import (
	"context"
	"fmt"
	"time"

	libptc "github.com/nabbar/golib/network/protocol"
	libsck "github.com/nabbar/golib/socket"

	sckcfg "github.com/nabbar/golib/socket/config"
	"github.com/nabbar/golib/socket/server/udp"
)

func main() {
	cfg := sckcfg.Server{
		Network: libptc.NetworkUDP,
		Address: ":9007",
	}

	handler := func(ctx libsck.Context) {
		buf := make([]byte, 1024)
		_, _ = ctx.Read(buf)
	}

	srv, err := udp.New(nil, handler, cfg)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Check initial state
	fmt.Printf("Running: %v\n", srv.IsRunning())
	fmt.Printf("Gone: %v\n", srv.IsGone())
	fmt.Printf("Connections: %d\n", srv.OpenConnections())

	// Start server
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go func() {
		_ = srv.Listen(ctx)
	}()

	// Wait for server to start
	time.Sleep(10 * time.Millisecond)

	fmt.Printf("After start - Running: %v\n", srv.IsRunning())
	fmt.Printf("After start - Gone: %v\n", srv.IsGone())
	fmt.Printf("After start - Connections: %d\n", srv.OpenConnections())

	// Shutdown
	cancel()
	time.Sleep(100 * time.Millisecond)

	fmt.Printf("After shutdown - Gone: %v\n", srv.IsGone())

}
Output:
Running: false
Gone: true
Connections: 0
After start - Running: true
After start - Gone: false
After start - Connections: 0
After shutdown - Gone: true

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidAddress is returned when the provided listen address fails validation.
	//
	// # Validation Logic
	//
	// The address is parsed using net.ResolveUDPAddr. Common valid formats:
	//   - ":port" (all interfaces, IPv4 and IPv6)
	//   - "0.0.0.0:port" (all IPv4 interfaces)
	//   - "127.0.0.1:port" (loopback only)
	//   - "[::1]:port" (IPv6 loopback)
	//
	// # Use Case
	//
	// Typically returned during RegisterServer() or at the start of Listen().
	ErrInvalidAddress = fmt.Errorf("invalid listen address")

	// ErrInvalidHandler is returned when trying to start the server with a nil handler.
	//
	// # Mandatory Requirement
	//
	// Since the server only spawns a single handler for all UDP traffic, a non-nil
	// HandlerFunc is required by the New() constructor.
	ErrInvalidHandler = fmt.Errorf("invalid handler")

	// ErrShutdownTimeout is returned when the graceful shutdown period expires.
	//
	// # Shutdown Logic
	//
	// When Shutdown(ctx) is called, the server:
	//   1. Closes the broadcast channel (gnc).
	//   2. Closes the listener socket.
	//   3. Monitors the IsRunning flag for cleanup completion.
	//
	// If the provided context 'ctx' times out before the cleanup is finished,
	// this error is returned.
	ErrShutdownTimeout = fmt.Errorf("timeout on stopping socket")

	// ErrInvalidInstance is returned when operating on a nil *srv pointer.
	//
	// # Defensive Coding
	//
	// This error prevents panics in scenarios where methods are called on an
	// uninitialized or nil server instance.
	ErrInvalidInstance = fmt.Errorf("invalid socket instance")
)

Functions

This section is empty.

Types

type ServerUdp added in v1.19.0

type ServerUdp interface {
	libsck.Server

	// RegisterServer configures the UDP address the server will bind to.
	//
	// # Requirements
	//
	//   1. Must be called BEFORE Listen().
	//   2. Address must be in the format "host:port" or ":port".
	//
	// Parameters:
	//   - address: String representing the network address.
	//
	// Returns:
	//   - error: ErrInvalidAddress if the address is empty or malformed.
	RegisterServer(address string) error
}

ServerUdp defines the specialized interface for a UDP datagram server.

Interface Hierarchy

This interface extends the core github.com/nabbar/golib/socket.Server but with several important semantic differences:

  • Stateless Nature: Unlike a TCP server, a UDP server doesn't "maintain" connections. OpenConnections() will always return 0 (or 1 if the socket is listening).
  • Datagram Management: The server is purely datagram-oriented; no automatic handshake or flow control is provided by this layer.
  • Synchronicity: Only one main handler goroutine is spawned per Listen().

Inherited Methods (from socket.Server)

  • Listen(context.Context) error: Starts the UDP listener. Blocks until the context is cancelled or Shutdown is called.
  • Shutdown(context.Context) error: Gracefully closes the listener and cleans up internal resources within the given context deadline.
  • Close() error: Immediate, non-blocking shutdown.
  • IsRunning() bool: Thread-safe check of whether the socket is listening.
  • IsGone() bool: Thread-safe check of whether the server is in the "Stopped" state.
  • RegisterFuncError(FuncError): Registers a callback for server-level errors.
  • RegisterFuncInfo(FuncInfo): Registers a callback for connection events.
  • RegisterFuncInfoSrv(FuncInfoSrv): Registers a callback for lifecycle messages.

func New

New creates and initializes a new UDP server instance.

Design Principle

This constructor uses a combination of mandatory and optional parameters to ensure the server is always in a valid, startable state.

Parameters:

  • upd: Optional UpdateConn callback. Use this to configure the raw socket (e.g., SetReadBuffer, SetWriteBuffer, JoinMulticastGroup).
  • hdl: Mandatory HandlerFunc. This is the main entry point for datagram processing. It is executed in a dedicated goroutine when Listen() starts.
  • cfg: Server configuration struct, providing the initial address and network protocol.

Implementation Notes

During initialization:

  1. Atomic values are allocated for all status flags and callbacks.
  2. A default (no-op) broadcast channel (gnc) is created to prevent nil dereferences.
  3. RegisterServer is called with the address provided in cfg.
  4. The server is initially marked as 'gon=true' (not running).

Returns:

  • ServerUdp: The initialized server instance.
  • error: If validation fails (e.g., nil handler or invalid address).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL