tcp

package
v1.20.0 Latest Latest
Warning

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

Go to latest
Published: Mar 10, 2026 License: MIT Imports: 12 Imported by: 0

README

TCP Client Package

Go Version License Coverage

Thread-safe TCP client with atomic state management, TLS support, connection lifecycle callbacks, and one-shot request/response operations for reliable network communication.


Table of Contents


Overview

The tcp package provides a high-performance, thread-safe TCP client implementation with atomic state management, TLS encryption support, and connection lifecycle callbacks. It's designed for reliable network communication with explicit control over connection state and error handling.

Design Philosophy
  1. Thread Safety First: All state management uses atomic operations for lock-free concurrency
  2. Explicit State Control: Clear connection lifecycle with IsConnected() status checking
  3. TLS Integration: First-class TLS support with certificate configuration
  4. Observable: Connection state and error callbacks for monitoring and logging
  5. Context-Aware: Full integration with Go's context for timeout and cancellation
Key Features
  • Atomic State Management: Thread-safe state tracking with github.com/nabbar/golib/atomic
  • TLS Support: Configurable TLS encryption with certificate management
  • Connection Callbacks: Error and info callbacks for lifecycle events
  • One-Shot Requests: Convenient Once() method for request/response patterns
  • Standard Interfaces: Implements io.Reader, io.Writer, io.Closer
  • Context Integration: Timeout and cancellation support for all operations
  • Connection Replacement: Automatic cleanup when reconnecting without explicit close
  • 79.4% Test Coverage: 159 comprehensive test specs with race detection

Architecture

Component Diagram
┌────────────────────────────────────────────────────────────┐
│                       ClientTCP                            │
├────────────────────────────────────────────────────────────┤
│                                                            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │         libatm.Map[uint8] (Atomic State)            │   │
│  │  - keyNetAddr: string (server address)              │   │
│  │  - keyNetConn: net.Conn (TCP connection)            │   │
│  │  - keyTLSCfg: *tls.Config (TLS configuration)       │   │
│  │  - keyFctErr: FuncError (error callback)            │   │
│  │  - keyFctInf: FuncInfo (info callback)              │   │
│  └─────────────────────────────────────────────────────┘   │
│                          │                                 │
│                          ▼                                 │
│  ┌─────────────────────────────────────────────────────┐   │
│  │           Connection Operations                     │   │
│  │                                                     │   │
│  │  Connect(ctx) ──▶ dial() ──▶ net.Conn               │   │
│  │       │                          │                  │   │
│  │       ├─▶ fctInfo(Dial)          │                  │   │
│  │       ├─▶ fctInfo(New)           │                  │   │
│  │       └─▶ fctError(on error)     │                  │   │
│  │                                  │                  │   │
│  │  Read(p) ────────────────────────┼─▶ conn.Read()    │   │
│  │       │                          │                  │   │
│  │       ├─▶ fctInfo(Read)          │                  │   │
│  │       └─▶ fctError(on error)     │                  │   │
│  │                                  │                  │   │
│  │  Write(p) ───────────────────────┼─▶ conn.Write()   │   │
│  │       │                          │                  │   │
│  │       ├─▶ fctInfo(Write)         │                  │   │
│  │       └─▶ fctError(on error)     │                  │   │
│  │                                  │                  │   │
│  │  Close() ────────────────────────┼─▶ conn.Close()   │   │
│  │       │                          │                  │   │
│  │       └─▶ fctInfo(Close)         │                  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │        Once() - One-Shot Request/Response           │   │
│  │                                                     │   │
│  │  1. Connect(ctx)                                    │   │
│  │  2. Write(request data)                             │   │
│  │  3. Response callback(client as io.Reader)          │   │
│  │  4. defer Close()                                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                            │
└────────────────────────────────────────────────────────────┘
State Management

The client uses atomic operations via github.com/nabbar/golib/atomic.Map[uint8] for thread-safe state management:

State Key Type Description
keyNetAddr string Server address (host:port)
keyNetConn net.Conn Active TCP connection
keyTLSCfg *tls.Config TLS configuration (if enabled)
keyFctErr FuncError Error callback function
keyFctInf FuncInfo Info callback function

Thread Safety: All operations are lock-free using atomic load/store/swap operations, ensuring safe concurrent access to connection state.

Connection Lifecycle
┌─────────┐     Connect()      ┌───────────┐
│  New()  │───────────────────▶│ Connected │
└─────────┘                    └───────────┘
     │                               │
     │                               │ Read/Write operations
     │                               │
     │         Close()               ▼
     └──────────────────────────▶ Closed
                                     │
                                     │ Connect() again
                                     │
                                     ▼
                              ┌───────────┐
                              │ Connected │
                              └───────────┘

State Callbacks:

  1. ConnectionDial: Triggered when dialing starts
  2. ConnectionNew: Triggered when connection is established
  3. ConnectionRead: Triggered on each read operation
  4. ConnectionWrite: Triggered on each write operation
  5. ConnectionClose: Triggered when connection is closed

Performance

Benchmarks

Performance measurements from test suite (AMD64, Go 1.25):

Operation Latency Throughput Notes
Connect ~10ms 100 conn/s TCP handshake + callbacks
Read (1KB) <1ms ~1MB/s Network-bound
Write (1KB) <1ms ~1MB/s Network-bound
Close ~5ms 200 ops/s Graceful shutdown
Once (echo) ~15ms 60 req/s Connect + I/O + Close
IsConnected <1µs 1M+ ops/s Atomic read

Measured on localhost, actual network performance varies with RTT and bandwidth

Memory Usage
Base overhead:        ~200 bytes (struct + atomics)
Per connection:       net.Conn overhead (~4KB)
TLS overhead:         +~16KB (TLS state machine)
Total per instance:   ~4-20KB depending on TLS

Memory Efficiency:

  • Atomic state management avoids mutex overhead
  • No buffering beyond net.Conn internal buffers
  • TLS session reuse reduces handshake overhead
Scalability
  • Concurrent Clients: Tested with up to 100 concurrent instances
  • Connection Pooling: Safe to create multiple clients per server
  • Zero Race Conditions: All tests pass with -race detector
  • Long-lived Connections: No memory leaks or goroutine leaks

Limitations:

  • One connection per client instance
  • Reads/writes are sequential (no concurrent I/O on same connection)
  • Network-bound performance (limited by TCP, not client implementation)

Use Cases

1. HTTP Client Alternative

Problem: Need lower-level TCP control without HTTP overhead.

client, _ := tcp.New("api.example.com:9000")
defer client.Close()

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

client.Connect(ctx)
client.Write([]byte("GET /api/data\n"))

buf := make([]byte, 4096)
n, _ := client.Read(buf)
response := buf[:n]
2. Database Protocol Client

Problem: Implement custom database wire protocol.

client, _ := tcp.New("db.example.com:5432")
defer client.Close()

// Send authentication
client.Connect(ctx)
client.Write(authPacket)

// Read response
response, _ := io.ReadAll(io.LimitReader(client, 1024))
3. Monitoring Service

Problem: Monitor TCP service availability.

client, _ := tcp.New("service:8080")

client.RegisterFuncInfo(func(local, remote net.Addr, state libsck.ConnState) {
    log.Printf("[%s] %s -> %s", state, local, remote)
})

err := client.Connect(ctx)
if err != nil {
    alert.Send("Service unavailable")
}
4. TLS Encrypted Communication

Problem: Secure communication with certificate validation.

client, _ := tcp.New("secure.example.com:443")

// Configure TLS
certConfig, _ := certificates.NewConfig(...)
client.SetTLS(true, certConfig, "secure.example.com")

// Connect with TLS handshake
client.Connect(ctx)
5. Request-Response Pattern

Problem: Simple request/response without persistent connection.

client, _ := tcp.New("service:9000")

request := bytes.NewBufferString("QUERY data\n")

err := client.Once(ctx, request, func(r io.Reader) {
    response, _ := io.ReadAll(io.LimitReader(r, 4096))
    processResponse(response)
})

Quick Start

Installation
go get github.com/nabbar/golib/socket/client/tcp
Basic Connection
package main

import (
    "context"
    "fmt"
    
    tcp "github.com/nabbar/golib/socket/client/tcp"
)

func main() {
    // Create client
    client, err := tcp.New("localhost:8080")
    if err != nil {
        panic(err)
    }
    defer client.Close()
    
    // Connect
    ctx := context.Background()
    err = client.Connect(ctx)
    if err != nil {
        panic(err)
    }
    
    fmt.Println("Connected:", client.IsConnected())
}
TLS Connection
package main

import (
    "context"
    
    tcp "github.com/nabbar/golib/socket/client/tcp"
    "github.com/nabbar/golib/certificates"
)

func main() {
    client, _ := tcp.New("secure.example.com:443")
    defer client.Close()
    
    // Load certificates
    certConfig, _ := certificates.NewConfig(
        certificates.WithCertificateFile("cert.pem"),
        certificates.WithPrivateKeyFile("key.pem"),
    )
    
    // Enable TLS
    err := client.SetTLS(true, certConfig, "secure.example.com")
    if err != nil {
        panic(err)
    }
    
    // Connect with TLS
    ctx := context.Background()
    client.Connect(ctx)
}
One-Shot Request
package main

import (
    "bytes"
    "context"
    "fmt"
    "io"
    
    tcp "github.com/nabbar/golib/socket/client/tcp"
)

func main() {
    client, _ := tcp.New("localhost:8080")
    
    ctx := context.Background()
    request := bytes.NewBufferString("HELLO\n")
    
    err := client.Once(ctx, request, func(r io.Reader) {
        // Read response
        buf := make([]byte, 1024)
        n, _ := r.Read(buf)
        fmt.Printf("Response: %s", buf[:n])
    })
    
    if err != nil {
        panic(err)
    }
}
With Callbacks
package main

import (
    "context"
    "log"
    "net"
    
    tcp "github.com/nabbar/golib/socket/client/tcp"
    "github.com/nabbar/golib/socket"
)

func main() {
    client, _ := tcp.New("localhost:8080")
    defer client.Close()
    
    // Register error callback
    client.RegisterFuncError(func(errs ...error) {
        for _, err := range errs {
            log.Printf("Error: %v", err)
        }
    })
    
    // Register info callback
    client.RegisterFuncInfo(func(local, remote net.Addr, state socket.ConnState) {
        log.Printf("[%s] %s -> %s", state, local, remote)
    })
    
    ctx := context.Background()
    client.Connect(ctx)
}
Read and Write
package main

import (
    "context"
    "fmt"
    
    tcp "github.com/nabbar/golib/socket/client/tcp"
)

func main() {
    client, _ := tcp.New("localhost:8080")
    defer client.Close()
    
    ctx := context.Background()
    client.Connect(ctx)
    
    // Write data
    message := []byte("Hello, Server!\n")
    n, err := client.Write(message)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Sent %d bytes\n", n)
    
    // Read response
    buf := make([]byte, 1024)
    n, err = client.Read(buf)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Received: %s", buf[:n])
}

Best Practices

Testing

The package includes a comprehensive test suite with 79.4% code coverage and 159 test specifications using BDD methodology (Ginkgo v2 + Gomega).

Key test coverage:

  • ✅ All public APIs and connection lifecycle operations
  • ✅ Concurrent access with race detector (zero races detected)
  • ✅ TLS configuration and handshake
  • ✅ Error handling and edge cases
  • ✅ Context integration and cancellation
  • ✅ Callback mechanisms

For detailed test documentation, see TESTING.md.

✅ DO

Context Management:

// Use context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

err := client.Connect(ctx)

Resource Cleanup:

// Always defer Close()
client, _ := tcp.New("localhost:8080")
defer client.Close()

client.Connect(ctx)

Error Handling:

// Check all errors
err := client.Connect(ctx)
if err != nil {
    return fmt.Errorf("connection failed: %w", err)
}

Connection State:

// Check before I/O
if !client.IsConnected() {
    return errors.New("not connected")
}

n, err := client.Write(data)
❌ DON'T

Don't ignore errors:

// ❌ BAD: Ignoring errors
client.Connect(ctx)
client.Write(data)

// ✅ GOOD: Check errors
if err := client.Connect(ctx); err != nil {
    return err
}

Don't use after Close:

// ❌ BAD: Use after close
client.Close()
client.Write(data)  // Returns error

// ✅ GOOD: Check state
if client.IsConnected() {
    client.Write(data)
}

Don't share across goroutines:

// ❌ BAD: Concurrent access
go func() { client.Read(buf1) }()
go func() { client.Read(buf2) }()  // Race condition!

// ✅ GOOD: One client per goroutine
for i := 0; i < 10; i++ {
    go func() {
        c, _ := tcp.New("localhost:8080")
        defer c.Close()
        c.Connect(ctx)
    }()
}

Don't block without timeout:

// ❌ BAD: No timeout
ctx := context.Background()
client.Connect(ctx)  // May hang forever

// ✅ GOOD: Use timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
client.Connect(ctx)

API Reference

ClientTCP Interface
type ClientTCP interface {
    // Connection management
    Connect(ctx context.Context) error
    Close() error
    IsConnected() bool
    
    // I/O operations
    Read(p []byte) (n int, err error)
    Write(p []byte) (n int, err error)
    
    // Configuration
    SetTLS(enable bool, config TLSConfig, serverName string) error
    
    // Callbacks
    RegisterFuncError(f FuncError)
    RegisterFuncInfo(f FuncInfo)
    
    // One-shot request/response
    Once(ctx context.Context, request io.Reader, fct Response) error
}

Methods:

  • Connect(ctx context.Context) error: Establishes TCP connection with optional TLS
  • Close() error: Closes connection and releases resources
  • IsConnected() bool: Returns current connection state (atomic read)
  • Read(p []byte) (n int, err error): Reads data from connection
  • Write(p []byte) (n int, err error): Writes data to connection
  • SetTLS(enable bool, config TLSConfig, serverName string) error: Configures TLS encryption
  • RegisterFuncError(f FuncError): Registers error callback
  • RegisterFuncInfo(f FuncInfo): Registers connection state callback
  • Once(ctx context.Context, request io.Reader, fct Response) error: One-shot request/response
Configuration

Constructor:

func New(address string) (ClientTCP, error)

Creates a new TCP client instance.

Parameters:

  • address - Server address in format "host:port" or ":port"

Returns: ClientTCP instance or error

Valid Address Formats:

tcp.New("localhost:8080")        // Hostname + port
tcp.New("192.168.1.1:8080")      // IPv4 + port
tcp.New("[::1]:8080")            // IPv6 + port
tcp.New(":8080")                 // Any interface + port
Callbacks

FuncError:

type FuncError func(errs ...error)

Called when errors occur during operations.

Example:

client.RegisterFuncError(func(errs ...error) {
    for _, err := range errs {
        log.Printf("Client error: %v", err)
    }
})

FuncInfo:

type FuncInfo func(local, remote net.Addr, state ConnState)

Called on connection state changes.

Connection States:

  • ConnectionDial: Dialing started
  • ConnectionNew: Connection established
  • ConnectionRead: Read operation performed
  • ConnectionWrite: Write operation performed
  • ConnectionClose: Connection closed

Example:

client.RegisterFuncInfo(func(local, remote net.Addr, state libsck.ConnState) {
    log.Printf("[%s] %s -> %s", state, local, remote)
})
Error Codes
var (
    ErrInstance    = errors.New("invalid client instance")
    ErrConnection  = errors.New("connection error")
    ErrAddress     = errors.New("invalid address")
)

Error Handling:

Error When Recovery
ErrInstance Nil client or after Close() Create new instance
ErrConnection No active connection Call Connect()
ErrAddress Invalid address format Fix address string
Network errors I/O failures Reconnect or handle gracefully

Contributing

Contributions are welcome! Please follow these guidelines:

  1. Code Quality

    • Follow Go best practices and idioms
    • Maintain or improve code coverage (target: >80%)
    • Pass all tests including race detector
    • Use gofmt and golint
  2. AI Usage Policy

    • AI must NEVER be used to generate package code or core functionality
    • AI assistance is limited to:
      • Testing (writing and improving tests)
      • Debugging (troubleshooting and bug resolution)
      • Documentation (comments, README, TESTING.md)
    • All AI-assisted work must be reviewed and validated by humans
  3. Testing

    • Add tests for new features
    • Use Ginkgo v2 / Gomega for test framework
    • Ensure zero race conditions
    • Maintain coverage above 75%
  4. Documentation

    • Update GoDoc comments for public APIs
    • Add examples for new features
    • Update README.md and TESTING.md if needed
  5. Pull Request Process

    • Fork the repository
    • Create a feature branch
    • Write clear commit messages
    • Ensure all tests pass
    • Update documentation
    • Submit PR with description of changes

Improvements & Security

Current Status

The package is production-ready with no urgent improvements or security vulnerabilities identified.

Code Quality Metrics
  • 79.4% test coverage (target: >80%, nearly achieved)
  • Zero race conditions detected with -race flag
  • Thread-safe implementation using atomic operations
  • Memory-safe with proper resource cleanup
  • Context integration for timeout and cancellation
Future Enhancements (Non-urgent)

The following enhancements could be considered for future versions:

  1. Connection Pooling: Reusable connection pool for high-throughput scenarios
  2. Keep-Alive Configuration: Configurable TCP keep-alive settings
  3. Retry Logic: Built-in exponential backoff for connection failures
  4. Metrics Export: Optional integration with Prometheus or other metrics systems
  5. Bandwidth Limiting: Configurable read/write rate limiting

These are optional improvements and not required for production use. The current implementation is stable and performant.


Resources

Package Documentation
  • GoDoc - Complete API reference with function signatures, method descriptions, and runnable examples. Essential for understanding the public interface and usage patterns.

  • doc.go - In-depth package documentation including design philosophy, architecture diagrams, state management, TLS configuration, and performance considerations. Provides detailed explanations of internal mechanisms and best practices for production use.

  • TESTING.md - Comprehensive test suite documentation covering test architecture, BDD methodology with Ginkgo v2, coverage analysis (79.4%), and guidelines for writing new tests. Includes troubleshooting and CI integration examples.

Standard Library References
  • net - Standard library networking package. The TCP client builds upon net.Conn and net.Dial for low-level TCP operations.

  • context - Standard library context package. The client fully implements context-based cancellation and timeout for all blocking operations.

  • crypto/tls - Standard library TLS package. Used for secure connection establishment when TLS is enabled.

External References
  • Effective Go - Official Go programming guide covering best practices for interfaces, error handling, and I/O patterns. The TCP client follows these conventions for idiomatic Go code.

  • TCP/IP Illustrated - Comprehensive guide to TCP/IP protocol suite. Useful for understanding low-level TCP behavior and tuning.


AI Transparency

In compliance with EU AI Act Article 50.4: AI assistance was used for testing, documentation, and bug resolution under human supervision. All core functionality is human-designed and validated.


License

MIT License - See LICENSE file for details.

Copyright (c) 2025 Nicolas JUHEL


Maintained by: Nicolas JUHEL
Package: github.com/nabbar/golib/socket/client/tcp
Version: See releases for versioning

Documentation

Overview

Package tcp provides a robust, production-ready TCP client implementation with support for TLS, connection management, and comprehensive monitoring capabilities.

Overview

This package implements a high-performance TCP client that supports:

  • Plain TCP and TLS/SSL encrypted connections with configurable cipher suites
  • Thread-safe connection management using atomic operations
  • Connection lifecycle monitoring with state callbacks
  • Context-aware operations with timeout and cancellation support
  • One-shot request/response pattern for simple protocols
  • Error reporting through callback functions

Architecture

## Component Diagram

The client follows a simple, efficient architecture with atomic state management:

┌─────────────────────────────────────────────────────┐
│                    TCP Client                       │
├─────────────────────────────────────────────────────┤
│                                                     │
│  ┌──────────────────────────────────────────┐       │
│  │         State (Atomic Map)               │       │
│  │  - Network Address (string)              │       │
│  │  - TLS Config (*tls.Config)              │       │
│  │  - Error Callback (FuncError)            │       │
│  │  - Info Callback (FuncInfo)              │       │
│  │  - Connection (net.Conn)                 │       │
│  └──────────────┬───────────────────────────┘       │
│                 │                                   │
│                 ▼                                   │
│  ┌──────────────────────────────────────────┐       │
│  │        Connection Operations             │       │
│  │  - Connect() - Establish connection      │       │
│  │  - Read() - Read data                    │       │
│  │  - Write() - Write data                  │       │
│  │  - Close() - Close connection            │       │
│  │  - Once() - Request/Response pattern     │       │
│  └──────────────────────────────────────────┘       │
│                                                     │
│  Optional Callbacks:                                │
│   - FuncError: Error notifications                  │
│   - FuncInfo: Connection state changes              │
│                                                     │
└─────────────────────────────────────────────────────┘

## State Management

The client uses an atomic map (libatm.Map[uint8]) to store all state in a thread-safe manner:

  • keyNetAddr (string): Target server address (host:port)
  • keyTLSCfg (*tls.Config): TLS configuration when encryption is enabled
  • keyFctErr (FuncError): Error callback function
  • keyFctInfo (FuncInfo): Connection state callback function
  • keyNetConn (net.Conn): Active network connection

This approach avoids nil pointer panics and eliminates the need for explicit locking, providing thread-safe access to client state.

## Data Flow

  1. New() creates client with address validation
  2. Optional: SetTLS() configures encryption
  3. Optional: Register callbacks for monitoring
  4. Connect() establishes connection: a. Context-aware dialing with timeout b. TLS handshake (if configured) c. Connection stored in atomic map d. ConnectionNew callback triggered
  5. Read()/Write() perform I/O: a. Validate connection exists b. Trigger ConnectionRead/Write callbacks c. Perform actual I/O operation d. Trigger error callback on failure
  6. Close() terminates connection: a. Trigger ConnectionClose callback b. Close underlying net.Conn c. Remove connection from state

## Thread Safety Model

Synchronization primitives used:

  • libatm.Map[uint8]: Lock-free atomic map for all state
  • No mutexes required: All operations are atomic

Concurrency guarantees:

  • All exported methods are safe for concurrent use
  • Multiple goroutines can call different methods safely
  • However, concurrent Read() or Write() calls on the same client are NOT recommended (net.Conn is not designed for concurrent I/O)

Best practice: Use one client instance per goroutine, or synchronize Read/Write operations externally if sharing is necessary.

Features

## Security

  • TLS 1.2/1.3 support with configurable settings
  • Server certificate validation
  • Optional client certificate authentication
  • SNI (Server Name Indication) support
  • Integration with github.com/nabbar/golib/certificates for TLS management

## Reliability

  • Context-aware connections with timeout and cancellation
  • Automatic connection validation before I/O
  • Error propagation through callbacks
  • Clean resource management with Close()
  • Connection state tracking with IsConnected()

## Monitoring & Observability

  • Connection state change callbacks (dial, new, read, write, close)
  • Error reporting through callback functions
  • Connection lifecycle notifications
  • Thread-safe state queries

## Performance

  • Zero-copy I/O where possible
  • Lock-free atomic operations for state management
  • Minimal memory overhead (~1KB per client)
  • Efficient connection reuse
  • Keep-alive support for long-lived connections (5-minute default)

Usage Examples

Basic TCP client:

import (
	"context"
	"fmt"
	"github.com/nabbar/golib/socket/client/tcp"
)

func main() {
	// Create client
	client, err := tcp.New("localhost:8080")
	if err != nil {
		panic(err)
	}
	defer client.Close()

	// Connect
	ctx := context.Background()
	if err := client.Connect(ctx); err != nil {
		panic(err)
	}

	// Send data
	data := []byte("Hello, server!")
	n, err := client.Write(data)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Sent %d bytes\n", n)

	// Read response
	buf := make([]byte, 1024)
	n, err = client.Read(buf)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Received: %s\n", buf[:n])
}

TLS client with monitoring:

import (
	"context"
	"log"
	"net"
	"github.com/nabbar/golib/certificates"
	"github.com/nabbar/golib/socket"
	tcp "github.com/nabbar/golib/socket/client/tcp"
)

func main() {
	// Create TLS config
	tlsConfig := certificates.New()
	// Configure certificates...

	// Create client
	client, _ := tcp.New("secure.example.com:443")
	defer client.Close()

	// Configure TLS
	client.SetTLS(true, tlsConfig, "secure.example.com")

	// Register callbacks
	client.RegisterFuncError(func(errs ...error) {
		for _, err := range errs {
			log.Printf("Client error: %v", err)
		}
	})

	client.RegisterFuncInfo(func(local, remote net.Addr, state socket.ConnState) {
		log.Printf("Connection %s: %s -> %s", state, local, remote)
	})

	// Connect and use client
	ctx := context.Background()
	if err := client.Connect(ctx); err != nil {
		log.Fatal(err)
	}

	// Perform operations...
}

One-shot request/response:

import (
	"bytes"
	"context"
	"fmt"
	"io"
	tcp "github.com/nabbar/golib/socket/client/tcp"
)

func main() {
	client, _ := tcp.New("localhost:8080")

	request := bytes.NewBufferString("GET / HTTP/1.0\r\n\r\n")

	ctx := context.Background()
	err := client.Once(ctx, request, func(reader io.Reader) {
		response, _ := io.ReadAll(reader)
		fmt.Printf("Response: %s\n", response)
	})

	if err != nil {
		panic(err)
	}
	// Connection automatically closed
}

Performance Considerations

## Throughput

The client's throughput is primarily limited by:

  1. Network bandwidth and latency
  2. Application-level processing in handlers
  3. TLS overhead (if enabled): ~10-30% CPU cost for encryption
  4. Buffer sizes for Read/Write operations

Typical performance (localhost):

  • Without TLS: ~500MB/s for large transfers
  • With TLS: ~350MB/s for large transfers
  • Small messages: Limited by round-trip time

## Latency

Expected latency profile:

┌──────────────────────┬─────────────────┐
│  Operation           │  Typical Time   │
├──────────────────────┼─────────────────┤
│  Connect()           │  1-10 ms        │
│  TLS handshake       │  1-5 ms         │
│  Read() syscall      │  <100 µs        │
│  Write() syscall     │  <100 µs        │
│  Close()             │  <1 ms          │
└──────────────────────┴─────────────────┘

## Memory Usage

Per-client memory allocation:

Client structure:     ~100 bytes
Atomic map:           ~200 bytes
Connection overhead:  ~8 KB (OS buffers)
TLS state:            ~5 KB (if enabled)
─────────────────────────────────
Total minimum:        ~8.3 KB per client
Total with TLS:       ~13.3 KB per client

## Concurrency Patterns

Best practices for concurrent usage:

  1. One Client Per Goroutine (Recommended):

    for i := 0; i < workers; i++ { go func() { client, _ := tcp.New("server:8080") defer client.Close() // Use client independently }() }

  2. Connection Pooling:

    // Implement a pool of clients for reuse type ClientPool struct { clients chan ClientTCP }

    func (p *ClientPool) Get() ClientTCP { return <-p.clients }

    func (p *ClientPool) Put(c ClientTCP) { p.clients <- c }

  3. Single Client with Synchronization (Not Recommended):

    // Only if you must share one client var mu sync.Mutex client, _ := tcp.New("server:8080")

    func sendData(data []byte) { mu.Lock() defer mu.Unlock() client.Write(data) }

Limitations

## Known Limitations

  1. No built-in connection pooling (implement at application level)
  2. No automatic reconnection (application must handle)
  3. No built-in retry logic (implement in handlers)
  4. No protocol-level framing (implement in application)
  5. No built-in multiplexing (use HTTP/2 or gRPC if needed)
  6. Not safe for concurrent Read/Write on same client (use multiple clients)

## Not Suitable For

  • HTTP/HTTPS clients (use net/http instead)
  • Protocols requiring multiplexing (use HTTP/2 or gRPC)
  • Ultra-high-frequency trading (<10µs latency requirements)
  • Shared client across many goroutines doing I/O simultaneously

## Comparison with Alternatives

┌──────────────────┬────────────────┬──────────────────┬──────────────┐
│  Feature         │  This Package  │  net.Dial        │  net/http    │
├──────────────────┼────────────────┼──────────────────┼──────────────┤
│  Protocol        │  Raw TCP       │  Any             │  HTTP/HTTPS  │
│  TLS             │  Built-in      │  Manual          │  Built-in    │
│  Callbacks       │  Yes           │  No              │  Limited     │
│  State Tracking  │  Yes           │  No              │  No          │
│  Context Support │  Yes           │  Yes             │  Yes         │
│  Complexity      │  Low           │  Very Low        │  Medium      │
│  Best For        │  Custom proto  │  Simple cases    │  HTTP only   │
└──────────────────┴────────────────┴──────────────────┴──────────────┘

Best Practices

## Error Handling

  1. Always register error callbacks for monitoring:

    client.RegisterFuncError(func(errs ...error) { for _, err := range errs { log.Printf("TCP error: %v", err) } })

  2. Check all error returns:

    n, err := client.Write(data) if err != nil { log.Printf("Write failed: %v", err) return err } if n != len(data) { log.Printf("Short write: %d of %d", n, len(data)) }

  3. Handle connection errors gracefully:

    if err := client.Connect(ctx); err != nil { // Implement retry logic if appropriate for attempt := 0; attempt < maxRetries; attempt++ { time.Sleep(backoff) if err = client.Connect(ctx); err == nil { break } } }

## Resource Management

  1. Always use defer for cleanup:

    client, err := tcp.New("server:8080") if err != nil { return err } defer client.Close() // Ensure cleanup

  2. Use context for timeouts:

    ctx, cancel := context.WithTimeout( context.Background(), 10*time.Second) defer cancel()

    if err := client.Connect(ctx); err != nil { log.Printf("Connection timeout: %v", err) }

  3. Check connection before operations:

    if !client.IsConnected() { if err := client.Connect(ctx); err != nil { return err } } // Now safe to Read/Write

## Security

  1. Always use TLS for production:

    tlsConfig := certificates.New() // Configure certificates... client.SetTLS(true, tlsConfig, "server.example.com")

  2. Validate server certificates:

    // TLS config should verify server identity // Don't skip certificate verification in production

  3. Use appropriate timeouts:

    ctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel()

  4. Sanitize data before sending:

    // Validate and sanitize input to prevent injection data := sanitizeInput(userInput) client.Write(data)

## Testing

  1. Test with mock servers:

    // Use local test server for unit tests srv := startTestServer() defer srv.Close()

    client, _ := tcp.New(srv.Addr()) // Test client behavior

  2. Test error conditions:

    // Test connection failures client, _ := tcp.New("invalid:99999") err := client.Connect(ctx) // Verify error handling

  3. Test with TLS:

    // Use self-signed certificates for testing // Test both successful and failed handshakes

  4. Run with race detector: go test -race

  • github.com/nabbar/golib/socket: Base interfaces and types
  • github.com/nabbar/golib/socket/client: Generic client interfaces
  • github.com/nabbar/golib/socket/server/tcp: TCP server implementation
  • github.com/nabbar/golib/certificates: TLS certificate management
  • github.com/nabbar/golib/network/protocol: Protocol constants
  • github.com/nabbar/golib/atomic: Thread-safe atomic operations

See the example_test.go file for runnable examples covering common use cases.

Package tcp provides a TCP client implementation with TLS support and callback mechanisms.

This package implements the github.com/nabbar/golib/socket.Client interface for TCP network connections. It supports both plain TCP and TLS-encrypted connections, provides connection state callbacks, error handling callbacks, and maintains connection state using thread-safe atomic operations via github.com/nabbar/golib/atomic.

Key features:

  • Plain TCP and TLS-encrypted connections
  • Thread-safe connection management using atomic.Map
  • Configurable error and info callbacks
  • Context-aware connection operations
  • Support for one-shot request/response operations

Basic usage:

// Create a new TCP client
client, err := tcp.New("localhost:8080")
if err != nil {
    log.Fatal(err)
}
defer client.Close()

// Connect to server
ctx := context.Background()
if err := client.Connect(ctx); err != nil {
    log.Fatal(err)
}

// Write data
n, err := client.Write([]byte("Hello"))
if err != nil {
    log.Fatal(err)
}

For TLS connections, see SetTLS method documentation.

Example

Example demonstrates the simplest TCP client usage. This is the most basic client implementation for quick start.

package main

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

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

	sckclt "github.com/nabbar/golib/socket/client/tcp"

	sckcfg "github.com/nabbar/golib/socket/config"

	scksrt "github.com/nabbar/golib/socket/server/tcp"
)

func main() {
	// Start a simple echo server for demonstration
	srv := startEchoServer(":8080")
	defer srv.Shutdown(context.Background())
	time.Sleep(50 * time.Millisecond) // Wait for server

	// Create client
	client, err := sckclt.New("localhost:8080")
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	// Connect to server
	ctx := context.Background()
	if err := client.Connect(ctx); err != nil {
		log.Fatal(err)
	}

	// Send data
	data := []byte("Hello")
	_, err = client.Write(data)
	if err != nil {
		log.Fatal(err)
	}

	// Read response
	buf := make([]byte, len(data))
	_, err = io.ReadFull(client, buf)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s", buf)
}

// Helper function to start a simple echo server for examples
func startEchoServer(addr string) scksrt.ServerTcp {
	handler := func(c libsck.Context) {
		defer c.Close()
		buf := make([]byte, 4096)
		for {
			n, err := c.Read(buf)
			if err != nil {
				return
			}
			if n > 0 {
				_, _ = c.Write(buf[:n])
			}
		}
	}

	cfg := sckcfg.Server{
		Network: libptc.NetworkTCP,
		Address: addr,
	}

	srv, err := scksrt.New(nil, handler, cfg)
	if err != nil {
		panic(err)
	}

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

	return srv
}
Output:

Hello
Example (Complete)

Example_complete demonstrates a production-ready client with all features. This example shows proper error handling, monitoring, and resource management.

package main

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

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

	sckclt "github.com/nabbar/golib/socket/client/tcp"

	sckcfg "github.com/nabbar/golib/socket/config"

	scksrt "github.com/nabbar/golib/socket/server/tcp"
)

func main() {
	// Start test server
	srv := startEchoServer(":8090")
	defer srv.Shutdown(context.Background())
	time.Sleep(50 * time.Millisecond)

	// Create client
	client, err := sckclt.New("localhost:8090")
	if err != nil {
		fmt.Printf("Failed to create client: %v\n", err)
		return
	}
	defer client.Close()

	// Register error callback
	client.RegisterFuncError(func(errs ...error) {
		for _, e := range errs {
			log.Printf("Error: %v", e)
		}
	})

	// Register info callback for monitoring
	client.RegisterFuncInfo(func(local, remote net.Addr, state libsck.ConnState) {
		log.Printf("Connection %s: %s -> %s", state, local, remote)
	})

	// Connect with timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := client.Connect(ctx); err != nil {
		fmt.Printf("Connection failed: %v\n", err)
		return
	}

	// Verify connection
	if !client.IsConnected() {
		fmt.Println("Not connected")
		return
	}

	// Send data
	message := []byte("Production message")
	n, err := client.Write(message)
	if err != nil {
		fmt.Printf("Write failed: %v\n", err)
		return
	}

	// Read response
	response := make([]byte, len(message))
	n, err = io.ReadFull(client, response)
	if err != nil {
		fmt.Printf("Read failed: %v\n", err)
		return
	}

	fmt.Printf("Sent and received %d bytes successfully\n", n)
}

// Helper function to start a simple echo server for examples
func startEchoServer(addr string) scksrt.ServerTcp {
	handler := func(c libsck.Context) {
		defer c.Close()
		buf := make([]byte, 4096)
		for {
			n, err := c.Read(buf)
			if err != nil {
				return
			}
			if n > 0 {
				_, _ = c.Write(buf[:n])
			}
		}
	}

	cfg := sckcfg.Server{
		Network: libptc.NetworkTCP,
		Address: addr,
	}

	srv, err := scksrt.New(nil, handler, cfg)
	if err != nil {
		panic(err)
	}

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

	return srv
}
Output:

Sent and received 18 bytes successfully
Example (MultipleOperations)

Example_multipleOperations demonstrates multiple read/write operations.

package main

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

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

	sckclt "github.com/nabbar/golib/socket/client/tcp"

	sckcfg "github.com/nabbar/golib/socket/config"

	scksrt "github.com/nabbar/golib/socket/server/tcp"
)

func main() {
	srv := startEchoServer(":8092")
	defer srv.Shutdown(context.Background())
	time.Sleep(50 * time.Millisecond)

	client, _ := sckclt.New("localhost:8092")
	defer client.Close()

	ctx := context.Background()
	_ = client.Connect(ctx)

	// Multiple operations
	for i := 1; i <= 3; i++ {
		msg := fmt.Sprintf("Msg%d", i)
		_, _ = client.Write([]byte(msg))

		buf := make([]byte, 4)
		_, _ = io.ReadFull(client, buf)
		fmt.Printf("%s ", buf)
	}
	fmt.Println()
}

// Helper function to start a simple echo server for examples
func startEchoServer(addr string) scksrt.ServerTcp {
	handler := func(c libsck.Context) {
		defer c.Close()
		buf := make([]byte, 4096)
		for {
			n, err := c.Read(buf)
			if err != nil {
				return
			}
			if n > 0 {
				_, _ = c.Write(buf[:n])
			}
		}
	}

	cfg := sckcfg.Server{
		Network: libptc.NetworkTCP,
		Address: addr,
	}

	srv, err := scksrt.New(nil, handler, cfg)
	if err != nil {
		panic(err)
	}

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

	return srv
}
Output:

Msg1 Msg2 Msg3
Example (Reconnection)

Example_reconnection demonstrates handling reconnection after connection loss.

package main

import (
	"context"
	"fmt"
	"time"

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

	sckclt "github.com/nabbar/golib/socket/client/tcp"

	sckcfg "github.com/nabbar/golib/socket/config"

	scksrt "github.com/nabbar/golib/socket/server/tcp"
)

func main() {
	srv := startEchoServer(":8091")
	time.Sleep(50 * time.Millisecond)

	client, _ := sckclt.New("localhost:8091")
	defer client.Close()

	ctx := context.Background()

	// Initial connection
	if err := client.Connect(ctx); err != nil {
		fmt.Printf("Initial connection failed: %v\n", err)
		return
	}
	fmt.Println("Connected")

	// Close connection explicitly
	_ = client.Close()

	// Check connection status after close
	if !client.IsConnected() {
		fmt.Println("Connection closed")
	}

	_ = srv.Shutdown(ctx)
	fmt.Println("Reconnection scenario demonstrated")
}

// Helper function to start a simple echo server for examples
func startEchoServer(addr string) scksrt.ServerTcp {
	handler := func(c libsck.Context) {
		defer c.Close()
		buf := make([]byte, 4096)
		for {
			n, err := c.Read(buf)
			if err != nil {
				return
			}
			if n > 0 {
				_, _ = c.Write(buf[:n])
			}
		}
	}

	cfg := sckcfg.Server{
		Network: libptc.NetworkTCP,
		Address: addr,
	}

	srv, err := scksrt.New(nil, handler, cfg)
	if err != nil {
		panic(err)
	}

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

	return srv
}
Output:

Connected
Connection closed
Reconnection scenario demonstrated

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrInstance is returned when a nil client instance is used for operations.
	// This typically indicates a programming error where a method is called on
	// a nil pointer or an uninitialized client.
	ErrInstance = fmt.Errorf("invalid instance")

	// ErrConnection is returned when attempting to perform I/O operations
	// on a client that is not connected, or when the underlying connection
	// is nil or invalid. Check IsConnected() before performing operations.
	ErrConnection = fmt.Errorf("invalid connection")

	// ErrAddress is returned by New() when the provided dial address is empty,
	// malformed, or cannot be resolved as a valid TCP address. The address
	// must be in the format "host:port" (e.g., "localhost:8080", "192.168.1.1:9000").
	ErrAddress = fmt.Errorf("invalid dial address")
)

Functions

This section is empty.

Types

type ClientTCP

type ClientTCP interface {
	libsck.Client
}

ClientTCP represents a TCP client that implements the socket.Client interface.

This interface extends github.com/nabbar/golib/socket.Client and provides all standard socket operations including:

  • Connect(ctx) - Establish connection to server
  • IsConnected() - Check connection status
  • Read(p []byte) - Read data from connection
  • Write(p []byte) - Write data to connection
  • Close() - Close the connection
  • Once(ctx, request, response) - One-shot request/response operation
  • SetTLS(enable, config, serverName) - Configure TLS encryption
  • RegisterFuncError(f) - Register error callback
  • RegisterFuncInfo(f) - Register connection state callback

All operations are thread-safe and use atomic operations internally. The client maintains connection state and automatically calls registered callbacks for errors and connection state changes.

See github.com/nabbar/golib/socket package for interface details.

func New

func New(address string) (ClientTCP, error)

New creates a new TCP client for the specified address.

The address parameter must be in the format "host:port", where:

  • host can be a hostname, IPv4 address, IPv6 address (in brackets), or empty for localhost
  • port must be a valid port number (1-65535)

Examples of valid addresses:

  • "localhost:8080"
  • "192.168.1.1:9000"
  • "[::1]:8080" (IPv6)
  • ":8080" (binds to all interfaces)

The client is created in a disconnected state. Use Connect() to establish the connection. The address is validated but no network connection is attempted during construction.

Returns:

  • ClientTCP: A new client instance if successful
  • error: ErrAddress if address is empty or malformed, or a net.Error if the address cannot be resolved as a valid TCP address

Example:

client, err := tcp.New("localhost:8080")
if err != nil {
    log.Fatal(err)
}
defer client.Close()
Example

ExampleNew demonstrates creating a new TCP client. This is the first step in using the client package.

package main

import (
	"fmt"

	sckclt "github.com/nabbar/golib/socket/client/tcp"
)

func main() {
	// Create client with server address
	client, err := sckclt.New("localhost:8080")
	if err != nil {
		fmt.Printf("Failed to create client: %v\n", err)
		return
	}

	// Client is created but not connected yet
	fmt.Printf("Client created for localhost:8080\n")
	_ = client
}
Output:

Client created for localhost:8080
Example (InvalidAddress)

ExampleNew_invalidAddress demonstrates error handling for invalid addresses.

package main

import (
	"fmt"

	sckclt "github.com/nabbar/golib/socket/client/tcp"
)

func main() {
	// Try to create client with empty address
	_, err := sckclt.New("")
	if err != nil {
		fmt.Println("Error: invalid dial address")
	}

	// Try to create client with invalid format
	_, err = sckclt.New("not-a-valid-address")
	if err != nil {
		fmt.Println("Error: address resolution failed")
	}
}
Output:

Error: invalid dial address
Error: address resolution failed

Jump to

Keyboard shortcuts

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