- 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
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
- SocketService verwenden: Nutze immer
SocketServiceals Haupt-API statt direkter Aufrufe der einzelnen Services - Value Objects: Verwende immer Value Objects (
SocketAddress,SocketType, etc.) statt primitiver Typen - Resource Management:
SocketResourcekümmert sich automatisch um Cleanup, aber manuelles Schließen ist für explizite Kontrolle möglich - Non-blocking I/O: Verwende
setNonBlocking()für Server-Sockets, um mehrere Verbindungen parallel zu handhaben - Connection Pooling: Nutze
SocketConnectionPoolfür Client-Verbindungen, die wiederverwendet werden sollen - Error Handling: Fange spezifische Exceptions (
SocketBindException,SocketConnectException, etc.) für präzise Fehlerbehandlung - PCNTL für Load Balancing: Verwende
PcntlSocketServerfür Socket-Server, die mehrere Worker-Prozesse benötigen - Async für Parallelität: Verwende
AsyncSocketServerfü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
- PCNTL Module Documentation - Für PCNTL-Integration
- Async Module Documentation - Für Async-Integration
- Framework Guidelines - Allgemeine Framework-Prinzipien