Files
michaelschiemer/docs/claude/sockets-module.md
Michael Schiemer 36ef2a1e2c
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
fix: Gitea Traefik routing and connection pool optimization
- Remove middleware reference from Gitea Traefik labels (caused routing issues)
- Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s)
- Add explicit service reference in Traefik labels
- Fix intermittent 504 timeouts by improving PostgreSQL connection handling

Fixes Gitea unreachability via git.michaelschiemer.de
2025-11-09 14:46:15 +01:00

18 KiB

Sockets Module Documentation

Übersicht

Das Sockets-Modul (src/Framework/Sockets/) bietet eine vollständige, type-safe Abstraktion über PHP's Socket-Erweiterung. Es ermöglicht:

  • Type-safe Socket-Operationen: Alle Socket-Operationen verwenden Value Objects statt primitiver Typen
  • TCP/UDP Server & Client: Vollständige Server- und Client-Implementierung
  • Unix Domain Sockets: Unterstützung für Unix Domain Sockets
  • PCNTL Integration: Socket-Server in geforkten Worker-Prozessen
  • Async Integration: Non-blocking Socket I/O mit Fibers
  • Connection Pooling: Automatisches Management von Socket-Verbindungen

Architektur

Modulstruktur

src/Framework/Sockets/
├── SocketService.php              # Haupt-Service (Facade)
├── SocketFactory.php              # Socket-Erstellung
├── SocketServer.php               # Server-Operationen
├── SocketClient.php               # Client-Operationen
├── SocketConnection.php           # Connection Wrapper
├── SocketConnectionPool.php       # Connection Pool Management
├── SocketsInitializer.php         # DI-Initializer
├── Exceptions/                    # Exception-Klassen
├── ValueObjects/                  # Type-safe Value Objects
└── Integration/                   # PCNTL & Async Integration

Value Objects

SocketAddress

Repräsentiert eine Socket-Adresse (IPv4, IPv6 oder Unix Domain Socket).

use App\Framework\Sockets\ValueObjects\SocketAddress;

// IPv4 Adresse
$address = SocketAddress::ipv4('127.0.0.1', 8080);

// IPv6 Adresse
$address = SocketAddress::ipv6('::1', 8080);

// Unix Domain Socket
$address = SocketAddress::unix('/tmp/socket.sock');

// Von String parsen
$address = SocketAddress::fromString('127.0.0.1:8080');
$address = SocketAddress::fromString('/tmp/socket.sock');

// Eigenschaften
$host = $address->getHost();           // null für Unix Sockets
$port = $address->getPort();           // null für Unix Sockets
$path = $address->getUnixPath();       // null für Network Sockets
$protocol = $address->getProtocol();   // SocketProtocol Enum
$isUnix = $address->isUnixSocket();
$isNetwork = $address->isNetworkSocket();
$string = $address->toString();        // "127.0.0.1:8080" oder "/tmp/socket.sock"

SocketType

Enum für Socket-Typen.

use App\Framework\Sockets\ValueObjects\SocketType;

SocketType::TCP        // SOCK_STREAM
SocketType::UDP        // SOCK_DGRAM
SocketType::RAW        // SOCK_RAW
SocketType::RDM        // SOCK_RDM
SocketType::SEQPACKET  // SOCK_SEQPACKET

// Methoden
$type->getName();           // "TCP", "UDP", etc.
$type->getValue();          // SOCK_STREAM, SOCK_DGRAM, etc.
$type->isReliable();        // true für TCP, SEQPACKET
$type->isConnectionless();  // true für UDP

SocketProtocol

Enum für Protokoll-Familien.

use App\Framework\Sockets\ValueObjects\SocketProtocol;

SocketProtocol::IPv4  // AF_INET
SocketProtocol::IPv6  // AF_INET6
SocketProtocol::UNIX  // AF_UNIX

// Methoden
$protocol->getName();     // "IPv4", "IPv6", "Unix Domain"
$protocol->getValue();    // AF_INET, AF_INET6, AF_UNIX
$protocol->isNetwork();  // true für IPv4/IPv6
$protocol->isUnix();      // true für Unix Domain

SocketOption

Enum für Socket-Optionen.

use App\Framework\Sockets\ValueObjects\SocketOption;

SocketOption::SO_REUSEADDR
SocketOption::SO_REUSEPORT
SocketOption::SO_KEEPALIVE
SocketOption::SO_LINGER
SocketOption::SO_RCVBUF
SocketOption::SO_SNDBUF
SocketOption::SO_RCVTIMEO
SocketOption::SO_SNDTIMEO
SocketOption::TCP_NODELAY
SocketOption::TCP_KEEPIDLE
SocketOption::TCP_KEEPINTVL
SocketOption::TCP_KEEPCNT

// Methoden
$option->getName();      // "SO_REUSEADDR", etc.
$option->getLevel();    // SOL_SOCKET oder SOL_TCP
$option->getValue();    // SO_REUSEADDR, etc.

SocketResource

Type-safe Wrapper für Socket-Ressourcen mit automatischem Cleanup.

use App\Framework\Sockets\ValueObjects\SocketResource;

$resource = SocketResource::fromResource($socket, $type, $protocol);

// Eigenschaften
$socketResource = $resource->getResource();  // Native Socket-Resource
$type = $resource->getType();               // SocketType
$protocol = $resource->getProtocol();       // SocketProtocol
$isClosed = $resource->isClosed();

// Manuelles Schließen (automatisch im Destructor)
$resource->close();

Core Services

SocketFactory

Factory für Socket-Erstellung mit automatischem Option-Setup.

use App\Framework\Sockets\SocketFactory;
use App\Framework\Sockets\ValueObjects\SocketAddress;
use App\Framework\Sockets\ValueObjects\SocketType;
use App\Framework\Sockets\ValueObjects\SocketProtocol;

$factory = new SocketFactory();

// TCP Socket erstellen
$address = SocketAddress::ipv4('127.0.0.1', 8080);
$socket = $factory->createTcp($address);

// UDP Socket erstellen
$socket = $factory->createUdp($address);

// Unix Domain Socket erstellen
$unixAddress = SocketAddress::unix('/tmp/socket.sock');
$socket = $factory->createUnix($unixAddress);

// Generische Erstellung
$socket = $factory->createServer(SocketType::TCP, SocketProtocol::IPv4);
$socket = $factory->createClient(SocketType::TCP, SocketProtocol::IPv4);

SocketServer

Server-Operationen für Socket-Verwaltung.

use App\Framework\Sockets\SocketServer;
use App\Framework\Sockets\ValueObjects\SocketAddress;
use App\Framework\Sockets\ValueObjects\SocketResource;

$server = new SocketServer();
$address = SocketAddress::ipv4('0.0.0.0', 8080);

// Socket binden
$server->bind($socket, $address);

// Auf Verbindungen lauschen
$server->listen($socket, 10);  // backlog = 10

// Verbindung akzeptieren (non-blocking)
$connection = $server->accept($socket);
if ($connection !== null) {
    // Verbindung verarbeiten
    $clientSocket = $connection->getSocket();
    $clientAddress = $connection->getAddress();
}

// Socket-Auswahl (select)
$read = [$socket];
$write = [];
$except = [];
$numReady = $server->select($read, $write, $except, 1);  // 1 Sekunde Timeout

// Non-blocking/Blocking Mode
$server->setNonBlocking($socket);
$server->setBlocking($socket);

SocketClient

Client-Operationen für Socket-Verbindungen.

use App\Framework\Sockets\SocketClient;
use App\Framework\Sockets\ValueObjects\SocketAddress;
use App\Framework\Sockets\ValueObjects\SocketResource;

$client = new SocketClient();
$address = SocketAddress::ipv4('127.0.0.1', 8080);

// Verbindung herstellen
$client->connect($socket, $address);

// Daten lesen
$data = $client->read($socket, 1024);  // null wenn keine Daten (non-blocking)

// Daten schreiben
$bytesWritten = $client->write($socket, "Hello, World!");

// Daten senden (mit Flags)
$bytesSent = $client->send($socket, $data, MSG_DONTWAIT);

// Daten empfangen
$data = $client->receive($socket, 1024, MSG_DONTWAIT);

// Verbindung schließen
$client->close($socket);

SocketConnection

Wrapper für Socket-Verbindungen mit Metadaten.

use App\Framework\Sockets\SocketConnection;

$connection = SocketConnection::create($socketResource, $address);

// Eigenschaften
$socket = $connection->getSocket();
$address = $connection->getAddress();
$connectionId = $connection->getConnectionId();
$isClosed = $connection->isClosed();

// Verbindung schließen
$connection->close();

SocketConnectionPool

Verwaltung mehrerer Socket-Verbindungen.

use App\Framework\Sockets\SocketConnectionPool;

$pool = new SocketConnectionPool(
    maxConnectionsPerAddress: 5,
    connectionTimeoutSeconds: 300
);

// Verbindung hinzufügen
$pool->add($connection);

// Verbindung entfernen
$pool->remove($connection);

// Verbindung abrufen
$connection = $pool->get($connectionId);
$connections = $pool->getConnectionsByAddress($address);
$connection = $pool->getConnectionForAddress($address);

// Dead Connections bereinigen
$removed = $pool->cleanupDeadConnections();

// Statistiken
$count = $pool->getConnectionCount();
$allConnections = $pool->getAllConnections();

SocketService

Haupt-Facade für alle Socket-Operationen.

use App\Framework\Sockets\SocketService;

$socketService = $container->get(SocketService::class);

// Socket-Erstellung
$socket = $socketService->createTcp($address);
$socket = $socketService->createUdp($address);
$socket = $socketService->createUnix($address);

// Server-Operationen
$socketService->bind($socket, $address);
$socketService->listen($socket, 10);
$connection = $socketService->accept($socket);
$socketService->setNonBlocking($socket);

// Client-Operationen
$socketService->connect($socket, $address);
$data = $socketService->read($socket, 1024);
$bytes = $socketService->write($socket, $data);

// Connection Pool
$socketService->addConnection($connection);
$socketService->removeConnection($connection);
$connection = $socketService->getConnection($connectionId);
$removed = $socketService->cleanupDeadConnections();

Integration

PCNTL Integration

Socket-Server in geforkten Worker-Prozessen für Load Balancing.

use App\Framework\Sockets\Integration\PcntlSocketServer;
use App\Framework\Sockets\SocketService;
use App\Framework\Pcntl\PcntlService;
use App\Framework\Sockets\ValueObjects\SocketAddress;
use App\Framework\Sockets\ValueObjects\SocketResource;

$socketService = $container->get(SocketService::class);
$pcntlService = $container->get(PcntlService::class);

$pcntlServer = new PcntlSocketServer(
    socketService: $socketService,
    pcntlService: $pcntlService,
    maxWorkers: 4
);

// Connection Handler setzen
$pcntlServer->setConnectionHandler(function (SocketConnection $connection) {
    // Verbindung verarbeiten
    $socket = $connection->getSocket();
    $data = $socketService->read($socket->getResource(), 1024);
    // ...
});

// Server starten
$address = SocketAddress::ipv4('0.0.0.0', 8080);
$socket = $socketService->createTcp($address);
$pcntlServer->start($socket, $address, workerCount: 4);

// Server stoppen (graceful shutdown)
$pcntlServer->stop();

Async Integration

Non-blocking Socket-Server mit Fibers für parallele Connection-Verarbeitung.

use App\Framework\Sockets\Integration\AsyncSocketServer;
use App\Framework\Sockets\SocketService;
use App\Framework\Async\FiberManager;
use App\Framework\Sockets\ValueObjects\SocketAddress;

$socketService = $container->get(SocketService::class);
$fiberManager = $container->get(FiberManager::class);

$asyncServer = new AsyncSocketServer(
    socketService: $socketService,
    fiberManager: $fiberManager
);

// Server starten (gibt Fiber zurück)
$address = SocketAddress::ipv4('0.0.0.0', 8080);
$socket = $socketService->createTcp($address);

$serverFiber = $asyncServer->startAsync(
    socket: $socket,
    address: $address,
    connectionHandler: function (SocketConnection $connection) {
        // Connection in separatem Fiber verarbeiten
        $socket = $connection->getSocket();
        
        // Non-blocking lesen
        $readFiber = $asyncServer->readAsync($connection, 1024);
        $data = $readFiber->start();
        
        // Non-blocking schreiben
        $writeFiber = $asyncServer->writeAsync($connection, "Response");
        $bytes = $writeFiber->start();
    }
);

// Server-Fiber starten
$serverFiber->start();

Beispiele

Einfacher TCP Server

use App\Framework\Sockets\SocketService;
use App\Framework\Sockets\ValueObjects\SocketAddress;

$socketService = $container->get(SocketService::class);
$address = SocketAddress::ipv4('0.0.0.0', 8080);

// Socket erstellen
$socket = $socketService->createTcp($address);

// Binden und lauschen
$socketService->bind($socket, $address);
$socketService->listen($socket, 10);
$socketService->setNonBlocking($socket);

// Hauptschleife
while (true) {
    // Verbindung akzeptieren
    $connection = $socketService->accept($socket);
    
    if ($connection !== null) {
        $clientSocket = $connection->getSocket();
        
        // Daten lesen
        $data = $socketService->read($clientSocket, 1024);
        
        if ($data !== null) {
            // Antwort senden
            $socketService->write($clientSocket, "Echo: " . $data);
        }
        
        // Verbindung schließen
        $connection->close();
    }
    
    usleep(10000);  // 10ms
}

TCP Client

use App\Framework\Sockets\SocketService;
use App\Framework\Sockets\ValueObjects\SocketAddress;

$socketService = $container->get(SocketService::class);
$address = SocketAddress::ipv4('127.0.0.1', 8080);

// Client-Socket erstellen
$socket = $socketService->createClient(
    SocketType::TCP,
    SocketProtocol::IPv4
);

// Verbinden
$socketService->connect($socket, $address);

// Daten senden
$socketService->write($socket, "Hello, Server!");

// Antwort lesen
$response = $socketService->read($socket, 1024);

// Verbindung schließen
$socketService->close($socket);

Unix Domain Socket Server

use App\Framework\Sockets\SocketService;
use App\Framework\Sockets\ValueObjects\SocketAddress;

$socketService = $container->get(SocketService::class);
$address = SocketAddress::unix('/tmp/mysocket.sock');

// Socket erstellen
$socket = $socketService->createUnix($address);

// Binden und lauschen
$socketService->bind($socket, $address);
$socketService->listen($socket, 10);

// Verbindungen akzeptieren
while (true) {
    $connection = $socketService->accept($socket);
    
    if ($connection !== null) {
        // Verarbeitung
        $socket = $connection->getSocket();
        $data = $socketService->read($socket, 1024);
        // ...
    }
}

Migration von bestehendem Code

Von socket_create() zu SocketFactory

Vorher:

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

Nachher:

$address = SocketAddress::ipv4('127.0.0.1', 8080);
$socket = $socketService->createTcp($address);

Von socket_bind()/socket_listen() zu SocketServer

Vorher:

socket_bind($socket, '127.0.0.1', 8080);
socket_listen($socket, 10);

Nachher:

$address = SocketAddress::ipv4('127.0.0.1', 8080);
$socketService->bind($socket, $address);
$socketService->listen($socket, 10);

Von socket_accept() zu SocketServer::accept()

Vorher:

$clientSocket = socket_accept($socket);

Nachher:

$connection = $socketService->accept($socket);
if ($connection !== null) {
    $clientSocket = $connection->getSocket();
}

Von socket_select() zu SocketServer::select()

Vorher:

socket_select($read, $write, $except, 1);

Nachher:

$numReady = $socketService->select($read, $write, $except, 1);

Von socket_read()/socket_write() zu SocketClient

Vorher:

$data = socket_read($socket, 1024);
socket_write($socket, $data, strlen($data));

Nachher:

$data = $socketService->read($socket, 1024);
$bytes = $socketService->write($socket, $data);

Von socket_close() zu SocketResource Destructor

Vorher:

socket_close($socket);

Nachher:

// Automatisch im Destructor, oder manuell:
$socket->close();

Fehlerbehandlung

Das Sockets-Modul verwendet spezifische Exceptions für verschiedene Fehlertypen:

use App\Framework\Sockets\Exceptions\SocketException;
use App\Framework\Sockets\Exceptions\SocketBindException;
use App\Framework\Sockets\Exceptions\SocketConnectException;
use App\Framework\Sockets\Exceptions\SocketReadException;

try {
    $socketService->bind($socket, $address);
} catch (SocketBindException $e) {
    // Bind-Fehler behandeln
    error_log("Failed to bind: " . $e->getMessage());
}

try {
    $socketService->connect($socket, $address);
} catch (SocketConnectException $e) {
    // Connect-Fehler behandeln
    error_log("Failed to connect: " . $e->getMessage());
}

try {
    $data = $socketService->read($socket, 1024);
} catch (SocketReadException $e) {
    // Read-Fehler behandeln
    if ($e->getMessage() === 'Socket connection closed') {
        // Verbindung geschlossen
    }
}

Best Practices

  1. SocketService verwenden: Nutze immer SocketService als Haupt-API statt direkter Aufrufe der einzelnen Services
  2. Value Objects: Verwende immer Value Objects (SocketAddress, SocketType, etc.) statt primitiver Typen
  3. Resource Management: SocketResource kümmert sich automatisch um Cleanup, aber manuelles Schließen ist für explizite Kontrolle möglich
  4. Non-blocking I/O: Verwende setNonBlocking() für Server-Sockets, um mehrere Verbindungen parallel zu handhaben
  5. Connection Pooling: Nutze SocketConnectionPool für Client-Verbindungen, die wiederverwendet werden sollen
  6. Error Handling: Fange spezifische Exceptions (SocketBindException, SocketConnectException, etc.) für präzise Fehlerbehandlung
  7. PCNTL für Load Balancing: Verwende PcntlSocketServer für Socket-Server, die mehrere Worker-Prozesse benötigen
  8. Async für Parallelität: Verwende AsyncSocketServer für non-blocking I/O mit Fibers

Dependency Injection

Alle Services werden automatisch über SocketsInitializer im DI-Container registriert:

use App\Framework\Sockets\SocketService;
use App\Framework\Sockets\SocketFactory;
use App\Framework\Sockets\SocketServer;
use App\Framework\Sockets\SocketClient;

// Services sind im Container verfügbar
$socketService = $container->get(SocketService::class);
$factory = $container->get(SocketFactory::class);
$server = $container->get(SocketServer::class);
$client = $container->get(SocketClient::class);

Framework-Kompatibilität

Das Sockets-Modul folgt allen Framework-Prinzipien:

  • Final readonly classes wo möglich
  • Value Objects statt Primitiven
  • Dependency Injection überall
  • Composition statt Inheritance
  • Strict Types (declare(strict_types=1))
  • PSR-12 Code Style

Siehe auch