Für einen performanteren Chat, war es nötig Server Send Events mit PHP und Javascript einzusetzen. Der Server sollte entlastet werden, dass nicht jede Sekunde ein AJAX Aufruf gestartet wird.
Der Vorteil von SSE ist, dass für eine Bestimmte Länge an Zeit eine Verbindung gehalten wird. In dieser Zeit, kann das PHP Script immer wieder Daten an das User Frontend schicken.
Im Kern ist es server-seitig eine PHP-Script File die in einer Dauerschleife ausgeführt wird und regelmäßig Daten zurück sendet. Im Frontend wird mit Javascript eine Event-Verbindung aufgebaut, die immer auf Daten wartet. Das praktische ist, dass nach dem beenden der Verbindung direkt eine Neue wieder aufgebaut wird.
Beispiel Code aus der MDN Dokumentation, der beim ersten mal so nicht geklappt hatte, aber gute weitere Informationen enthält.
Das folgende Script wird 5 Minuten ausgeführt und gibt alle 2 Sekunden Daten einer Chat-Log Datei zurück. Die Chat-Log Datei wird per GET-Parameter erfragt. Außerdem wird eine eigene Log File optional geschrieben um den Prozess zu überwachen.
Hinweise:
<?php session_start(); session_write_close(); header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); /* Aufruf URL: /chat-sse.php?chatlog_id=0 */ // === CONFIG === $max_exection_time_per_session = 300; // Maximale Laufzeit, 300sek = 5min $timeout_between_data_sends = 2; // Delay Timer, 1sek Pause $write_logfile = true; // Für Debugging eine File schreiben $logfilename = "chat-sse.log"; // File Name für Debug ausgabe $chatlog_id = ( isset ( $_GET['chatlog_id'] ) ) ? $_GET['chatlog_id'] : "0"; $chatlog_to_read = "$chatlog_id.json"; $min_string_padding = 400000; $max_string_padding = 401000; $fallback_data = [ // Wird zurück gegeben, wenn die File noch nicht existiert 'chatroom' => [ 'active_users' => [], 'latest_messages' => [], 'messages_having_attributes' => [], ], ]; $fallback_repsonse = json_encode( $fallback_data ); ignore_user_abort( true ); // ironischerweise hilft das besser raus zu finden ob User abbrechen oder nicht ini_set( 'max_execution_time', $max_exection_time_per_session ); $php_execution_time = ini_get( 'max_execution_time' ); $app_start_timestamp = time(); $current_readable_time = date( 'Y-m-d H:i:s'); // Check ob es ein neuer Stream ist oder nicht - magisch irgendwie $lastEventId = floatval(isset($_SERVER["HTTP_LAST_EVENT_ID"]) ? $_SERVER["HTTP_LAST_EVENT_ID"] : 0); if ($lastEventId == 0) { $lastEventId = floatval(isset($_GET["lastEventId"]) ? $_GET["lastEventId"] : 0); } if( $write_logfile ){ file_put_contents( $logfilename, "$current_readable_time $run_time :: User Connected" . PHP_EOL, FILE_APPEND ); } $run_loop = true; while ( $run_loop ) { // === AUSGABE === // Wenn alles normal läuft, die statische Chatfile ausgeben $does_chatlog_exist = file_exists( $chatlog_to_read ); $chatlog_filecontent = ( $does_chatlog_exist ) ? file_get_contents( $chatlog_to_read ) : $fallback_repsonse; $random_pad_generator = rand( $min_string_padding, $max_string_padding ); $server_cache_buster = str_pad( $message, $random_pad_generator ); echo "data: $chatlog_filecontent" . $server_cache_buster . PHP_EOL . PHP_EOL; // Weiterer Cache Flush für Server ob_flush(); flush(); // === Kontrolle ob Script gestoppt werden kann === //auf Wunsch werden die Gründe für Script abbrüche geloggt $current_timestamp = time(); $run_time = $current_timestamp - $app_start_timestamp; if(connection_aborted()){ if( $write_logfile ){ file_put_contents( $logfilename, "$current_readable_time $run_time :: ABORTED: Connection aborted." . PHP_EOL, FILE_APPEND ); } break; } if(connection_status() != CONNECTION_NORMAL){ if( $write_logfile ){ file_put_contents( $logfilename, "$current_readable_time $run_time :: ABORTED: Connection Status not normal." . PHP_EOL, FILE_APPEND ); } break; } if( ($php_execution_time + 5) < $run_time ){ if( $write_logfile ){ file_put_contents( $logfilename, "$current_readable_time $run_time :: ABORTED: Execution Time exceeded." . PHP_EOL, FILE_APPEND ); } break; } // Sekunde warten bevor es wieder ausgeführt wird und Daten sendet sleep( $timeout_between_data_sends ); } if( $write_logfile ){ file_put_contents( $logfilename, "$current_readable_time $run_time :: RUN ENDED: While Loop was finished." . PHP_EOL, FILE_APPEND ); } exit();
Auf der Frontend Seite ist es leichter. IE-Kompatibilität ist nicht gegeben. Mehr Events und die Doku findet man im MDN dazu.
const sse_url = '/chat-sse.php?chatlog_id=0'; const sse_connection = new EventSource( sse_url ); sse_connection.onmessage = function( response ) { const response_json = JSON.parse( response.data ); console.log( response_json ); }; sse_connection.onerror = function(err) { console.error("EventSource failed:", err); };