Files
golib/socket/server/udp/example_test.go
nabbar 3837f0b2bb Improvements, test & documentatons (2025-12 #1)
[file/bandwidth]
- ADD documentation: add enhanced README and TESTING guidelines
- ADD tests: complete test suites with benchmarks, concurrency, and edge cases

[file/perm]
- ADD documentation: add enhanced README and TESTING guidelines
- ADD tests: complete test suites with benchmarks, concurrency, and edge cases
- ADD function to parse form "rwx-wxr-x" or "-rwx-w-r-x"
- ADD function to ParseFileMode to convert os.FileMode to file.Perm

[file/progress]
- ADD documentation: add enhanced README and TESTING guidelines
- ADD tests: complete test suites with benchmarks, concurrency, and edge cases

[ioutils/...]
- UPDATE documentation: update enhanced README and TESTING guidelines
- UPDATE tests: complete test suites with benchmarks, concurrency, and edge cases

[logger/...]
- UPDATE documentation: update enhanced README and TESTING guidelines
- ADD documentation: add enhanced README and TESTING guidelines for sub
  packages
- UPDATE tests: complete test suites with benchmarks, concurrency, and edge cases
- UPDATE config: remove FileBufferSize from OptionFile (rework hookfile)
- UPDATE fields: expose Store function in interface
- REWORK hookfile: rework package, use aggregator to allow multi write and
  single file
- FIX hookstderr: fix bug with NonColorable
- FIX hookstdout: fix bug with NonColorable
- FIX hookwriter: fix bug with NonColorable

[network/protocol]
- ADD function IsTCP, IsUDP, IsUnixLike to check type of protocol

[runner]
- FIX typo

[socket]
- UPDATE documentation: update enhanced README and TESTING guidelines
- ADD documentation: add enhanced README and TESTING guidelines for sub
  packages
- UPDATE tests: complete test suites with benchmarks, concurrency, and edge cases
- REWORK server: use context compatible io.reader, io.writer, io.closer
  instead of reader / writer
- REWORK server: simplify, optimize server
- REMOVE reader, writer type
- ADD context: add new interface in root socket interface to expose
  context interface that extend context, io reader/writer/closer,
dediacted function to server (IsConnected, ...)
2025-12-02 02:56:20 +01:00

465 lines
11 KiB
Go

/*
* MIT License
*
* Copyright (c) 2025 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
*/
package udp_test
import (
"context"
"fmt"
"io"
"log"
"net"
"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"
)
// Example_basicServer demonstrates the simplest UDP server setup.
//
// This example shows minimal configuration for a UDP echo server
// that receives datagrams and logs them.
func Example_basicServer() {
// 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_echoServer demonstrates a complete UDP echo server.
//
// This example shows a server that echoes back received datagrams
// to the sender using ReadFrom/WriteTo pattern.
func Example_echoServer() {
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_serverWithCallbacks demonstrates callback registration.
//
// This example shows how to register callbacks for error handling,
// connection monitoring, and server lifecycle events.
func Example_serverWithCallbacks() {
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 demonstrates custom socket options.
//
// This example shows how to use UpdateConn callback to configure
// socket buffer sizes and other options.
func Example_socketConfiguration() {
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_gracefulShutdown demonstrates proper server shutdown.
//
// This example shows how to handle graceful shutdown using context
// cancellation and the Shutdown() method.
func Example_gracefulShutdown() {
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 {
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_multicastServer demonstrates handling multicast UDP traffic.
//
// This example shows configuration for a server that can receive
// multicast datagrams (concept demonstration).
func Example_multicastServer() {
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_errorHandling demonstrates comprehensive error handling.
//
// This example shows how to handle various error scenarios including
// invalid configuration, connection errors, and I/O errors.
func Example_errorHandling() {
// 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_stateMonitoring demonstrates server state monitoring.
//
// This example shows how to monitor server state using IsRunning(),
// IsGone(), and OpenConnections() methods.
func Example_stateMonitoring() {
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
}
// 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.
func Example_configFromStruct() {
// 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_integrationTest demonstrates testing UDP servers.
//
// This example shows how to write integration tests for UDP servers
// by creating a server and client for testing.
func Example_integrationTest() {
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
// Output: Integration test server ready
}