Documentation
¶
Overview ¶
Package tcp provides a high-performance, production-ready TCP server implementation with support for TLS, connection pooling, and centralized idle management.
1. ARCHITECTURE ¶
The TCP server is engineered to handle massive concurrency while maintaining a predictable and low memory footprint. It achieves this by combining Go's standard library net.TCPListener with several advanced architectural patterns.
┌─────────────────────────────────────────────────────────────────────────────┐ │ TCP SERVER SYSTEM │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ [ NETWORK LAYER ] [ KERNEL SPACE ] [ USER SPACE ] │ │ │ │ │ │ │ TCP SYN Package ----> [ TCP Accept Queue ] ----> [ Accept Loop (srv) ] │ │ │ │ │ ▼ │ │ [ RESOURCE MANAGEMENT ] [ CONNECTION HANDLER ] │ │ │ │ │ │ ┌──────────────┐ ┌────────────────┐ ┌───────────────┐ │ │ │ sync.Pool │ <─────── │ Context Reset │ <─── │ net.Conn │ │ │ │ (sCtx items) │ └────────────────┘ │ (Raw Socket) │ │ │ └──────────────┘ └───────┬───────┘ │ │ ^ │ │ │ │ v │ │ ┌──────────────┐ ┌────────────────┐ ┌───────────────┐ │ │ │ Idle Manager │ <─────── │ Registration │ <─── │ User Handler │ │ │ │ (sckidl.Mgr) │ └────────────────┘ │ (Goroutine) │ │ │ └──────────────┘ └───────────────┘ │ │ │ │ │ [ SHUTDOWN CONTROL ] v │ │ ┌──────────────────┐ ┌───────────────┐ │ │ │ Gone Channel │ ───────(Broadcast)─────────> │ Cleanup │ │ │ │ (gnc) │ │ (Pool Return) │ │ │ └──────────────────┘ └───────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘
2. PERFORMANCE OPTIMIZATIONS ¶
Zero-Allocation Connection Path: The server uses a sync.Pool to manage connection contexts (sCtx). Instead of allocating a new context for every client, the server fetches one from the pool, resets its internal state (atomic counters, context propagation), and returns it once the connection is closed. This reduces GC pause times by up to 90% in high-load scenarios.
Synchronous Acceptance Loop: The listener loop blocks directly on net.Listener.Accept(). Recent performance profiling showed that using intermediate channels for connection distribution introduced unnecessary scheduling overhead.
Centralized Idle Scanning: Replaced individual per-connection tickers with integration into the nabbar/golib/socket/idlemgr. A single background scanner handles thousands of timeouts by checking atomic activity counters, reducing the overall "timer" overhead in the Go runtime.
Event-Driven Shutdown (Gone Channel): The 'gnc' channel acts as a broadcast mechanism. When the server starts shutting down, this channel is closed. All active connection goroutines select on this channel, allowing them to terminate gracefully and instantly.
Systematic NoDelay: TCP_NODELAY is enabled by default to ensure minimal latency for small packets, bypassing Nagle's algorithm.
3. DATA FLOW ¶
The following diagram illustrates the lifecycle of a connection within the server:
[CLIENT] [SERVER LISTENER] [IDLE MGR] [HANDLER] │ │ │ │ │───(TCP Connect)────>│ │ │ │ │───(Fetch sCtx)───────>│ │ │ │ │ │ │ │───(Register)─────────>│ │ │ │ │ │ │ │───(Spawn Handler)────────────────────────>│ │ │ │ │ │<───(I/O Activity)───┼───────────────────────────────────────────│ │ │ │ │ │ │ │<──(Atomic Reset)──│ │ │ │ │ │───(TCP Close)──────>│ │ │ │ │───(Unregister)───────>│ │ │ │ │ │ │ │───(Release sCtx)─────>│ │ │ │ │ │
4. SECURITY & TLS (RFC 8446, RFC 5246) RFC 8446, RFC 5246)" aria-label="Go to 4. SECURITY & TLS (RFC 8446, RFC 5246)">¶
The server provides first-class support for TLS 1.2 and 1.3 through the SetTLS method. It integrates seamlessly with the nabbar/golib/certificates package.
## TLS over Pure Sockets: Limitations and Constraints
When using TLS over pure sockets (without a higher-level protocol like HTTP/1.1 or HTTP/2 which provide explicit host headers), several security and validation constraints must be acknowledged:
Trust Chain Relativity: Since the TLS handshake often happens on a direct IP dial, the verification of the certificate's Common Name (CN) or Subject Alternative Name (SAN) is relative to the IP or the provided ServerName.
SNI Constraints (RFC 6066): Server Name Indication (SNI) is used to select the appropriate certificate. If the client does not provide SNI, the server falls back to its default certificate. In pure socket environments, clients must be explicitly configured to send SNI for proper virtual hosting support.
RFC 8446 (TLS 1.3): The server prioritizes TLS 1.3, which eliminates vulnerable cryptographic primitives and provides "0-RTT" (not enabled by default for security reasons).
Trust Chain Validation: Chain of trust verification is performed by the underlying crypto/tls package. However, in pure socket scenarios, the lack of standardized application-level verification (like HTTP's HSTS) means the security of the initial handshake is paramount.
5. BEST PRACTICES & USE CASES ¶
- Use Case: Building high-performance microservices communicating over TCP.
- Use Case: Implementing custom protocols (e.g., database drivers, legacy IPC).
- Best Practice: Always call defer ctx.Close() within your HandlerFunc to ensure resource return even if a panic occurs.
- Best Practice: Use the UpdateConn callback for OS-level tuning like SO_RCVBUF and SO_SNDBUF for high-bandwidth applications.
6. IMPLEMENTATION EXAMPLE ¶
package main
import (
"context"
"fmt"
"github.com/nabbar/golib/socket"
"github.com/nabbar/golib/socket/config"
"github.com/nabbar/golib/socket/server/tcp"
)
func main() {
// 1. Define the handler
handler := func(ctx socket.Context) {
defer ctx.Close()
buf := make([]byte, 1024)
n, _ := ctx.Read(buf)
fmt.Printf("Received: %s\n", string(buf[:n]))
ctx.Write([]byte("ACK"))
}
// 2. Setup Config
cfg := config.Server{
Network: "tcp",
Address: ":9090",
}
// 3. Instantiate
srv, _ := tcp.New(nil, handler, cfg)
// 4. Start
srv.Listen(context.Background())
}
Package tcp provides a robust and performance-oriented TCP server implementation. It integrates with nabbar/golib/socket for common interfaces and configuration.
Features include:
- TLS support (v1.2, v1.3) with certificate management.
- Graceful shutdown with connection draining.
- High-performance memory pooling (sync.Pool).
- Centralized idle connection scanning.
- TCP_NODELAY and Keep-Alive tuning.
Example ¶
Example demonstrates a minimal TCP echo server. This is the simplest possible implementation for development and testing.
package main
import (
"context"
"time"
libptc "github.com/nabbar/golib/network/protocol"
libsck "github.com/nabbar/golib/socket"
sckcfg "github.com/nabbar/golib/socket/config"
scksrt "github.com/nabbar/golib/socket/server/tcp"
)
func main() {
// 1. Define the handler function
handler := func(c libsck.Context) {
// Ensure connection is closed after the handler exits.
defer func() { _ = c.Close() }()
buf := make([]byte, 1024)
for {
// Read from the context-aware wrapper.
n, err := c.Read(buf)
if err != nil {
return
}
// Write received data back (Echo).
if n > 0 {
_, _ = c.Write(buf[:n])
}
}
}
// 2. Create server configuration
cfg := sckcfg.Server{
Network: libptc.NetworkTCP,
Address: ":8080",
}
// 3. Instantiate the server
srv, err := scksrt.New(nil, handler, cfg)
if err != nil {
panic(err)
}
// 4. Start listening in a background goroutine
ctx := context.Background()
go func() {
_ = srv.Listen(ctx)
}()
// Wait briefly for the listener to bind.
time.Sleep(100 * time.Millisecond)
// 5. Gracefully shutdown the server.
_ = srv.Shutdown(ctx)
}
Output:
Example (Complete) ¶
Example_complete demonstrates a robust, production-ready TCP server. This example includes error handling, connection monitoring, and graceful shutdown.
package main
import (
"context"
"fmt"
"net"
"time"
libdur "github.com/nabbar/golib/duration"
libptc "github.com/nabbar/golib/network/protocol"
libsck "github.com/nabbar/golib/socket"
sckcfg "github.com/nabbar/golib/socket/config"
scksrt "github.com/nabbar/golib/socket/server/tcp"
)
func main() {
// Advanced handler with activity check
handler := func(c libsck.Context) {
defer func() { _ = c.Close() }()
buf := make([]byte, 4096)
// Use IsConnected() to check the socket state in a loop.
for c.IsConnected() {
n, err := c.Read(buf)
if err != nil {
return
}
if n > 0 {
if _, err := c.Write(buf[:n]); err != nil {
return
}
}
}
}
// Configure with a 5-minute idle timeout (centralized management).
cfg := sckcfg.Server{
Network: libptc.NetworkTCP,
Address: ":8081",
ConIdleTimeout: libdur.Minutes(5),
}
// Create server
srv, err := scksrt.New(nil, handler, cfg)
if err != nil {
fmt.Printf("Failed to create server: %v\n", err)
return
}
// Register monitoring callbacks for observability
srv.RegisterFuncError(func(errs ...error) {
for _, e := range errs {
fmt.Printf("Server error: %v\n", e)
}
})
srv.RegisterFuncInfo(func(local, remote net.Addr, state libsck.ConnState) {
fmt.Printf("Connection %s from %s\n", state, remote)
})
// Start server with context support
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
if err := srv.Listen(ctx); err != nil {
fmt.Printf("Server stopped: %v\n", err)
}
}()
// Active polling for server readiness
for i := 0; i < 25; i++ {
if srv.IsRunning() {
break
}
time.Sleep(100 * time.Millisecond)
}
if !srv.IsRunning() {
fmt.Println("Server not started")
}
fmt.Printf("Server running with %d connections\n", srv.OpenConnections())
// Shutdown with a specific timeout for connection draining
shutdownCtx, shutdownCancel := context.WithTimeout(
context.Background(), 10*time.Second)
defer shutdownCancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
fmt.Printf("Shutdown error: %v\n", err)
}
fmt.Println("Server stopped gracefully")
}
Output: Server running with 0 connections Server stopped gracefully
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidAddress is returned when the provided server address is empty, // malformed, or cannot be resolved using net.ResolveTCPAddr. // // Valid address examples: // - "127.0.0.1:8080" (IPv4 localhost) // - "[::1]:8080" (IPv6 localhost) // - ":8080" (All interfaces) ErrInvalidAddress = fmt.Errorf("invalid listen address") // ErrInvalidHandler is returned when the required HandlerFunc is not provided // during server initialization via New(). // // The handler must be a function matching the libsck.HandlerFunc signature // and is responsible for processing each client connection. ErrInvalidHandler = fmt.Errorf("invalid handler") // ErrShutdownTimeout is returned when the graceful shutdown process exceeds // the provided context's deadline. // // This error occurs during the draining phase, when active connections // fail to close or finish their task within the allocated time. ErrShutdownTimeout = fmt.Errorf("timeout on stopping socket") // ErrInvalidInstance is returned when a method is called on a nil server instance // or an instance that has not been properly initialized via New(). ErrInvalidInstance = fmt.Errorf("invalid socket instance") )
Functions ¶
This section is empty.
Types ¶
type ServerTcp ¶
type ServerTcp interface {
libsck.Server
// RegisterServer sets the TCP address for the server to listen on.
// The address should be in "host:port" format (e.g., "localhost:8080" or ":8080").
// Must be called before Listen(). Returns ErrInvalidAddress if the input
// is malformed.
RegisterServer(address string) error
}
ServerTcp defines the interface for a high-performance TCP server. It extends the base libsck.Server interface with specific TCP functionality.
A ServerTcp provides a concurrent TCP server that handles client connections using a customizable handler function. It supports TLS encryption, idle connection management, and graceful shutdown.
Thread Safety ¶
All implementations of ServerTcp MUST be safe for concurrent use by multiple goroutines. All methods can be called simultaneously from different threads.
Lifecycle Management ¶
- Creation: New() initializes the server with configuration and handler.
- Configuration: via SetTLS() and RegisterFunc* methods (Error, Info, InfoServer).
- Bind: via RegisterServer() to set the listen address.
- Operation: via Listen() to start accepting connections.
- Shutdown: via Shutdown() (graceful) or Close() (immediate).
Example Usage (Echo Server) ¶
hdl := func(ctx libsck.Context) {
defer ctx.Close()
io.Copy(ctx, ctx) // Echo data back
}
cfg := sckcfg.DefaultServer(":8080")
srv, _ := tcp.New(nil, hdl, cfg)
srv.Listen(context.Background())
Example (ContextValues) ¶
ExampleServerTcp_contextValues shows how to use the context for request-scoped data.
package main
import (
"context"
"fmt"
libptc "github.com/nabbar/golib/network/protocol"
libsck "github.com/nabbar/golib/socket"
sckcfg "github.com/nabbar/golib/socket/config"
scksrt "github.com/nabbar/golib/socket/server/tcp"
)
func main() {
type contextKey string
const userIDKey contextKey = "userID"
handler := func(c libsck.Context) {
defer func() { _ = c.Close() }()
// The context wrapper (sCtx) delegates Value() calls to the parent context.
if userID := c.Value(userIDKey); userID != nil {
fmt.Printf("Processing request for user: %v\n", userID)
}
}
cfg := sckcfg.Server{
Network: libptc.NetworkTCP,
Address: ":9011",
}
srv, _ := scksrt.New(nil, handler, cfg)
fmt.Println("Server with context values ready")
_ = srv.Shutdown(context.Background())
}
Output: Server with context values ready
Example (IdleTimeout) ¶
ExampleServerTcp_idleTimeout shows how inactivity thresholds are enforced.
package main
import (
"context"
"fmt"
"time"
libdur "github.com/nabbar/golib/duration"
libptc "github.com/nabbar/golib/network/protocol"
libsck "github.com/nabbar/golib/socket"
sckcfg "github.com/nabbar/golib/socket/config"
scksrt "github.com/nabbar/golib/socket/server/tcp"
)
func main() {
handler := func(c libsck.Context) {
defer func() { _ = c.Close() }()
// Connection remains idle. The idlemgr will terminate it.
time.Sleep(200 * time.Millisecond)
}
cfg := sckcfg.Server{
Network: libptc.NetworkTCP,
Address: ":9007",
ConIdleTimeout: libdur.ParseDuration(100 * time.Millisecond),
}
srv, _ := scksrt.New(nil, handler, cfg)
ctx := context.Background()
go func() {
_ = srv.Listen(ctx)
}()
time.Sleep(50 * time.Millisecond)
fmt.Println("Server with idle timeout running")
_ = srv.Shutdown(ctx)
}
Output: Server with idle timeout running
Example (Monitoring) ¶
ExampleServerTcp_monitoring shows a full setup of observability callbacks.
package main
import (
"context"
"fmt"
"net"
libptc "github.com/nabbar/golib/network/protocol"
libsck "github.com/nabbar/golib/socket"
sckcfg "github.com/nabbar/golib/socket/config"
scksrt "github.com/nabbar/golib/socket/server/tcp"
)
func main() {
handler := func(c libsck.Context) {
defer func() { _ = c.Close() }()
}
cfg := sckcfg.Server{
Network: libptc.NetworkTCP,
Address: ":9009",
}
srv, _ := scksrt.New(nil, handler, cfg)
// Register all available notification hooks.
srv.RegisterFuncError(func(errs ...error) {
fmt.Println("Error callback registered")
})
srv.RegisterFuncInfo(func(local, remote net.Addr, state libsck.ConnState) {
fmt.Println("Connection callback registered")
})
srv.RegisterFuncInfoServer(func(msg string) {
fmt.Println("Server info callback registered")
})
fmt.Println("All monitoring callbacks configured")
_ = srv.Shutdown(context.Background())
}
Output: All monitoring callbacks configured
func New ¶
func New(upd libsck.UpdateConn, hdl libsck.HandlerFunc, cfg sckcfg.Server) (ServerTcp, error)
New creates and initializes a new TCP server instance with the provided configuration.
Configuration and Initialization Dataflow ¶
- Validation: cfg.Validate() ensures basic parameters (address, timeouts) are sound.
- Defaults: Default TLS versions (1.2/1.3) and empty callbacks are set.
- Structure: The srv internal structure is allocated.
- Resource Pooling: The sync.Pool for sCtx recycling is initialized.
- Idle Manager: If ConIdleTimeout > 0, an sckidl.Manager is started to handle timeouts.
- Binding: RegisterServer() is called with the address from the config.
- Security: SetTLS() is called with TLS settings from the config.
- State: gon is set to true (server is ready to be started).
Parameters ¶
- upd: Optional callback to configure each net.Conn (e.g., buffer sizes) before handling.
- hdl: Required handler function that will be called for each new connection.
- cfg: Server configuration structure including address, TLS settings, and timeouts.
Returns ¶
- ServerTcp: The initialized server instance.
- error: Initialization errors (ErrInvalidHandler, ErrInvalidAddress, sckidl errors).
Example ¶
ExampleNew shows how to initialize a server and handle initial validation errors.
package main
import (
"fmt"
"io"
libptc "github.com/nabbar/golib/network/protocol"
libsck "github.com/nabbar/golib/socket"
sckcfg "github.com/nabbar/golib/socket/config"
scksrt "github.com/nabbar/golib/socket/server/tcp"
)
func main() {
handler := func(c libsck.Context) {
defer func() { _ = c.Close() }()
_, _ = io.Copy(c, c)
}
// Note: Providing a non-TCP protocol will fail validation for scksrt.New
cfg := sckcfg.Server{
Network: libptc.NetworkTCP,
Address: ":9000",
}
// Create server
srv, err := scksrt.New(nil, handler, cfg)
if err != nil {
fmt.Printf("Failed to create server: %v\n", err)
return
}
fmt.Printf("Server created successfully\n")
_ = srv
}
Output: Server created successfully
Example (SimpleProtocol) ¶
ExampleNew_simpleProtocol shows a line-based TCP server implementation.
package main
import (
"context"
"fmt"
libptc "github.com/nabbar/golib/network/protocol"
libsck "github.com/nabbar/golib/socket"
sckcfg "github.com/nabbar/golib/socket/config"
scksrt "github.com/nabbar/golib/socket/server/tcp"
)
func main() {
handler := func(c libsck.Context) {
defer func() { _ = c.Close() }()
buf := make([]byte, 1024)
for {
n, err := c.Read(buf)
if err != nil {
return
}
// Process and response logic (Echo for simplicity)
if n > 0 {
_, _ = c.Write(buf[:n])
}
}
}
cfg := sckcfg.Server{
Network: libptc.NetworkTCP,
Address: ":9010",
}
srv, err := scksrt.New(nil, handler, cfg)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println("Line-based protocol server created")
_ = srv.Shutdown(context.Background())
}
Output: Line-based protocol server created
Example (WithUpdateConn) ¶
ExampleNew_withUpdateConn shows how to tune low-level socket options.
package main
import (
"context"
"fmt"
"net"
"time"
libptc "github.com/nabbar/golib/network/protocol"
libsck "github.com/nabbar/golib/socket"
sckcfg "github.com/nabbar/golib/socket/config"
scksrt "github.com/nabbar/golib/socket/server/tcp"
)
func main() {
// Use UpdateConn for advanced OS-level tuning.
upd := func(c net.Conn) {
if tcpConn, ok := c.(*net.TCPConn); ok {
_ = tcpConn.SetKeepAlive(true)
_ = tcpConn.SetKeepAlivePeriod(30 * time.Second)
}
}
handler := func(c libsck.Context) {
defer func() { _ = c.Close() }()
}
cfg := sckcfg.Server{
Network: libptc.NetworkTCP,
Address: ":9008",
}
srv, err := scksrt.New(upd, handler, cfg)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println("Server with custom connection config created")
_ = srv.Shutdown(context.Background())
}
Output: Server with custom connection config created