167 lines
3.7 KiB
PHP
167 lines
3.7 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Http;
|
|
|
|
/**
|
|
* Repräsentiert eine aktive WebSocket-Verbindung
|
|
*/
|
|
final class WebSocketConnection
|
|
{
|
|
private bool $isConnected = true;
|
|
private array $attributes = [];
|
|
|
|
public function __construct(
|
|
private readonly string $id,
|
|
private $socket
|
|
) {}
|
|
|
|
/**
|
|
* Sendet eine Text-Nachricht
|
|
*/
|
|
public function send(string $message): bool
|
|
{
|
|
if (!$this->isConnected) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
$frame = $this->createFrame($message, 0x1); // Text frame
|
|
return fwrite($this->socket, $frame) !== false;
|
|
} catch (\Throwable $e) {
|
|
$this->disconnect();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sendet JSON-Daten
|
|
*/
|
|
public function sendJson(array $data): bool
|
|
{
|
|
return $this->send(json_encode($data));
|
|
}
|
|
|
|
/**
|
|
* Sendet Binärdaten
|
|
*/
|
|
public function sendBinary(string $data): bool
|
|
{
|
|
if (!$this->isConnected) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
$frame = $this->createFrame($data, 0x2); // Binary frame
|
|
return fwrite($this->socket, $frame) !== false;
|
|
} catch (\Throwable $e) {
|
|
$this->disconnect();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sendet einen Ping
|
|
*/
|
|
public function ping(string $data = ''): bool
|
|
{
|
|
if (!$this->isConnected) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
$frame = $this->createFrame($data, 0x9); // Ping frame
|
|
return fwrite($this->socket, $frame) !== false;
|
|
} catch (\Throwable $e) {
|
|
$this->disconnect();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Schließt die Verbindung
|
|
*/
|
|
public function close(int $code = 1000, string $reason = ''): void
|
|
{
|
|
if (!$this->isConnected) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$payload = pack('n', $code) . $reason;
|
|
$frame = $this->createFrame($payload, 0x8); // Close frame
|
|
fwrite($this->socket, $frame);
|
|
} catch (\Throwable $e) {
|
|
// Fehler beim Senden des Close-Frames ignorieren
|
|
}
|
|
|
|
$this->disconnect();
|
|
}
|
|
|
|
/**
|
|
* Trennt die Verbindung
|
|
*/
|
|
public function disconnect(): void
|
|
{
|
|
$this->isConnected = false;
|
|
if (is_resource($this->socket)) {
|
|
fclose($this->socket);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Erstellt einen WebSocket-Frame
|
|
*/
|
|
private function createFrame(string $payload, int $opcode): string
|
|
{
|
|
$payloadLength = strlen($payload);
|
|
$frame = '';
|
|
|
|
// Erste Byte: FIN (1) + RSV (000) + Opcode (4 bits)
|
|
$frame .= chr(0x80 | $opcode);
|
|
|
|
// Payload-Länge
|
|
if ($payloadLength < 126) {
|
|
$frame .= chr($payloadLength);
|
|
} elseif ($payloadLength < 65536) {
|
|
$frame .= chr(126) . pack('n', $payloadLength);
|
|
} else {
|
|
$frame .= chr(127) . pack('J', $payloadLength);
|
|
}
|
|
|
|
return $frame . $payload;
|
|
}
|
|
|
|
/**
|
|
* Setzt ein Attribut für diese Verbindung
|
|
*/
|
|
public function setAttribute(string $key, mixed $value): void
|
|
{
|
|
$this->attributes[$key] = $value;
|
|
}
|
|
|
|
/**
|
|
* Gibt ein Attribut zurück
|
|
*/
|
|
public function getAttribute(string $key, mixed $default = null): mixed
|
|
{
|
|
return $this->attributes[$key] ?? $default;
|
|
}
|
|
|
|
/**
|
|
* Prüft, ob die Verbindung aktiv ist
|
|
*/
|
|
public function isConnected(): bool
|
|
{
|
|
return $this->isConnected;
|
|
}
|
|
|
|
/**
|
|
* Gibt die Verbindungs-ID zurück
|
|
*/
|
|
public function getId(): string
|
|
{
|
|
return $this->id;
|
|
}
|
|
}
|