WebSocket in PHP: Von der Theorie zur industriellen Echtzeit-Anwendung
Das WebSocket-Protokoll hat die Webentwicklung revolutioniert, indem es die Erstellung vollwertiger bidirektionaler Kommunikationskanäle zwischen Client und Server ermöglicht. Im Gegensatz zu klassischem AJAX (Long Polling, Server-Sent Events) bietet WebSocket eine dauerhafte Verbindung mit minimalem Overhead. Für PHP-Entwickler ist der Übergang von einfachen „Echo-Server“-Beispielen zu einer industriellen Echtzeit-Anwendung eine ernsthafte Herausforderung, die mit Zustandsverwaltung, Skalierung und asynchroner Verarbeitung verbunden ist.
In diesem Artikel betrachten wir fortgeschrittene Techniken für die Arbeit mit WebSocket in PHP: die Architektur eines Mehrbenutzer-Chats, die Integration mit Redis für horizontale Skalierung, die Verarbeitung von Hintergrundaufgaben und die Leistungsoptimierung.
1. Architektur eines fortgeschrittenen WebSocket-Servers mit Ratchet
Die Bibliothek Ratchet (basierend auf ReactPHP) bleibt der Goldstandard für WebSocket in PHP. Für Produktionslösungen reicht jedoch ein einfaches onMessage nicht aus. Betrachten wir eine komponentenbasierte Architektur mit getrennten Verantwortlichkeiten:
<?phpuse Ratchet\\MessageComponentInterface;use Ratchet\\ConnectionInterface;use React\\EventLoop\\LoopInterface;
class RealTimeHub implements MessageComponentInterface { private $clients; private $loop; private $redis; private $userRegistry;
public function __construct(LoopInterface $loop, \\Redis $redis) { $this->clients = new \\SplObjectStorage; $this->loop = $loop; $this->redis = $redis; $this->userRegistry = []; }
public function onOpen(ConnectionInterface $conn) { $this->clients->attach($conn); echo "Neue Verbindung: {$conn->resourceId}"; }
public function onMessage(ConnectionInterface $from, $msg) { $data = json_decode($msg, true);
switch ($data['type'] ?? '') { case 'auth': $this->handleAuth($from, $data); break; case 'message': $this->handleMessage($from, $data); break; case 'subscribe': $this->handleSubscribe($from, $data); break; default: $from->send(json_encode(['error' => 'Unbekannter Befehl'])); } }
public function onClose(ConnectionInterface $conn) { $this->clients->detach($conn); // Bereinigung der Benutzerregistrierung if (isset($conn->userId)) { unset($this->userRegistry[$conn->userId]); } }
public function onError(ConnectionInterface $conn, \\Exception $e) { $conn->close(); }
private function handleAuth(ConnectionInterface $conn, array $data) { $token = $data['token'] ?? ''; // Überprüfung des JWT-Tokens $userId = $this->validateToken($token); if ($userId) { $conn->userId = $userId; $this->userRegistry[$userId] = $conn; $conn->send(json_encode(['type' => 'auth_ok'])); } else { $conn->send(json_encode(['type' => 'auth_fail'])); } }
private function handleMessage(ConnectionInterface $from, array $data) { $targetUserId = $data['to'] ?? null; if ($targetUserId && isset($this->userRegistry[$targetUserId])) { $this->userRegistry[$targetUserId]->send(json_encode([ 'type' => 'message', 'from' => $from->userId ?? 'anonymous', 'text' => $data['text'] ])); } }
private function validateToken(string $token): ?int { // Simulation der JWT-Überprüfung $payload = json_decode(base64_decode(explode('.', $token)[1] ?? ''), true); return $payl