main.go
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"time"
)

func runUDPEchoServer(ctx context.Context, addr string) (*net.UDPConn, error) {
	udpAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		return nil, err
	}
	conn, err := net.ListenUDP("udp", udpAddr)
	if err != nil {
		return nil, err
	}

	go func() {
		defer conn.Close()
		buf := make([]byte, 2048)
		for {
			select {
			case <-ctx.Done():
				return
			default:
			}
			_ = conn.SetReadDeadline(time.Now().Add(1 * time.Second))
			n, peer, err := conn.ReadFromUDP(buf)
			if err != nil {
				if ne, ok := err.(net.Error); ok && ne.Timeout() {
					continue
				}
				return
			}
			_ = conn.SetWriteDeadline(time.Now().Add(1 * time.Second))
			conn.WriteToUDP(buf[:n], peer)
		}
	}()

	return conn, nil
}

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

	server, err := runUDPEchoServer(ctx, "127.0.0.1:0")
	if err != nil {
		log.Fatalf("server: %v", err)
	}

	serverAddr := server.LocalAddr().String()
	log.Printf("udp echo server on %s", serverAddr)

	client, err := net.Dial("udp", serverAddr)
	if err != nil {
		log.Fatalf("dial: %v", err)
	}
	defer client.Close()

	msg := []byte("hello udp")
	client.SetWriteDeadline(time.Now().Add(1 * time.Second))
	if _, err := client.Write(msg); err != nil {
		log.Fatalf("write: %v", err)
	}

	buf := make([]byte, 2048)
	client.SetReadDeadline(time.Now().Add(1 * time.Second))
	n, err := client.Read(buf)
	if err != nil {
		log.Fatalf("read: %v", err)
	}
	fmt.Println(string(buf[:n]))
}

How It Works

Simple UDP echo pair showing packet send and receive with deadlines and graceful shutdown loops.

Server listens on UDP, reads datagrams with a read deadline, echoes payloads back, and exits on context cancel; client dials UDP, sends messages, reads responses with per-operation timeouts, then closes sockets.

Key Concepts

  • 1Per-operation deadlines keep both server and client from blocking indefinitely.
  • 2Loop handles network errors and context cancellation gracefully.
  • 3Demonstrates basic UDP read and write semantics without connections.

When to Use This Pattern

  • Baseline for custom UDP protocols or game servers.
  • Network troubleshooting tools.
  • Education on datagram versus stream socket behavior.

Best Practices

  • Size buffers to expected packet sizes and handle truncation.
  • Validate source addresses if echoing to avoid amplification issues.
  • Shut down listeners on context cancel to free the port quickly.
Go Version1.18+
Difficultybeginner
Production ReadyYes
Lines of Code77