Java Museum: Java Magazin kostenlos im entwickler.kiosk lesen!

Push it mit HTML5!

WebSocket-Implementierung mit PHP
Kommentare

Die WebSocket-Technik ist ein Teil der HTML-5-Spezifikation und dient zur bidirektionalen Kommunikation zwischen Server und Browser. Es soll gezeigt werden, wie WebSockets funktionieren und welche Unterschiede zu bisherigen Comet-Lösungen bestehen. Die Funktionsweise auf Serverseite werden wir beispielhaft am Aufbau einer Socket-Verbindung und einer kleinen Chatapplikation erläutern. Weiterhin werden die Anforderungen auf Clientseite aufgezeigt und ein Ausblick auf mögliche Anwendungen gegeben.

Bisher war die Kommunikation im Internet klar geregelt. Der Server durfte nur “sprechen”, wenn er auch gefragt wurde. Dieses Verfahren wird als Pull-Technologie bezeichnet, da sich der Client alle Informationen vom Server ziehen muss. Im Web 2.0 wurde es aber immer öfter notwendig, dass bestimmte Informationen unmittelbar vom Server an die Clients weitergeleitet werden mussten (Push-Technologie, da die Informationen vom Server zum Clienten geschoben werden). Beispiele für solche Anwendungen sind Chat- und Nachrichtensysteme oder aber auch Browserspiele mit Mehrspielermodus. Bisher wurde die Push-Technologie durch verschiedene Verfahren simuliert. Eine Möglichkeit ist es, ein IFrame in die Seite einzubinden und eine Anfrage an den Server zu senden, die erst beantwortet wird, wenn der Server eine Nachricht an den Clienten senden will. Diese Technik hat aber viele Nachteile, z. B. können Proxys oder Firewalls diese Art von langen Anfragen unterbinden, weiterhin wird eine weitere Verbindung zum Server benötigt, die diesen unnötig belastet. Eine weitere Möglichkeit ist die Nutzung des XMLHttpRequests, die durch eine Multipart-Nachricht des Servers eine Stream-Verbindung von Server zu Client ermöglicht. Das XMLHttpRequest-Objekt ist auch vom W3C spezifiziert worden und stellt eine Alternative zu den neuen WebSockets dar. Der Nachteil des XMLHttpRequest-Objekts ist, dass es in den verschiedenen Browsern unterschiedlich implementiert wurde. Eine weitere Möglichkeit die Push-Technologie zu simulieren ist das ständige Nachfragen des Clients, ob der Server eine neue Nachricht für den Clienten hat. Diese Techniken werden oft auch als “Comet”-Lösungen bezeichnet. Im TCP-/IP-Bereich oder auch in der Interprozesskommunikation gibt es aber längst bessere und standardisierte Techniken. Es haben sich hier Socket-Verbindungen zwischen Server und Client durchgesetzt. Vorteile und Eigenschaften von Socket-Verbindungen:
  • persistent (sie bleiben nach jeder Nachricht weiterhin bestehen, bis sie geschlossen werden)
  • bidirektional (das Senden ist in beide Richtungen jederzeit möglich)
  • performant (kein großer Protokoll-Overhead, wie bei Request-/Response-Techniken)
Mit HTML 5 gibt es nun auch eine Spezifikation für WebSockets, die eine bidirektionale Socket-Verbindung zwischen Client und Server ermöglicht, in der Nachrichten in beide Richtungen gesendet werden können. Vom W3C wurde das allgemeine Clientinterface spezifiziert, die erste Version stammt bereits vom 23. April 2009 und liegt aktuell in einer Version vom 1. Juni 2010 vor. Es gibt auch eine Erweiterung der WHATWG-Gruppe mit einigen Erweiterungen, die über die Spezifikation des W3C hinausgehen.

Funktionsweise von Socket-Verbindungen

Ein Socket ist ein Kommunikationsendpunkt. Es werden dabei die Techniken und Funktionen der untersten vier Ebenen des OSI- (Open System Interconnection Reference) Models gekapselt. Um eine Socket-Verbindung herzustellen, benötigt man einen Server, der auf einer Adresse und einem Port “lauscht” und auf eingehende Verbindungen wartet. Dieser Vorgang wird als “binden” (bind) bezeichnet, da man den Socket an die Adresse und den Port bindet, an der er Anfragen akzeptiert. Das warten auf eingehende Verbindungen wird als “lauschen” (listen) bezeichnet. Versucht sich nun ein Client mit dem Server zu verbinden, so wird aus dem “lauschenden” Socket ein neuer Socket abgeleitet, der für die Verbindung zwischen diesem speziellen Clienten und dem Server zuständig ist. Der “lauschende” Socket bleibt dabei erhalten und wartet auf weitere Clientverbindungen. Der Server muss nun die Clientverbindungen verwalten und kann über diese Nachrichten an bestimmte oder alle Clients senden. Socket-Verbindungen bleiben erhalten, auch nachdem eine Nachricht gesendet wurde und erlauben über die ClientSockets Nachrichtenaustausch in beide Richtungen.

Server für Socket-Verbindungen mit PHP

Da WebSocket-Verbindungen nichts anderes sind als normale Socket-Verbindungen, können die in PHP ab Version 4.1.0 enthaltenen Funktionen genutzt werden, um einen Server für WebSocket-Verbindungen zu erstellen. Um die PHP-internen Socket-Funktionen nutzen zu können, muss ihr PHP mit der Socket-Unterstützung kompiliert sein. Dies erreichen sie unter UNIX mit der –enable-sockets-Option während der Konfiguration. Unter Windows müssen sie lediglich die entsprechende Bibliothek php_sockets.dll in der php.ini laden. Um nun eine Socket-Verbindung aufzubauen, muss als Erstes der Socket erstellt werden. Dies geschieht in PHP mit der Funktion socket_create. Sie benötigt drei Parameter: die Protokollfamilie, den Typ und das Protokoll. Da die WebSockets auf dem TCP/IP(-Protokoll) basieren, wählen wir SOL_TCP als Protokoll und AF_INET als Protokollfamilie. Der SocketType sollte auf SOCK_STREAM gestellt werden. Dem Socket muss natürlich eine Adresse und eine Portnummer zugewiesen werden, an die er gebunden werden soll. Dies geschieht mit der Funktion socket_bind. Nun muss man den Socket noch dazu bringen, auf eingehende Verbindungen zu warten, dies geschieht durch die Funktion socket_listen. Wurden diese Funktionen erfolgreich aufgerufen, kann mittels socket_select das Array mit den Sockets (am Anfang nur der ServerSocket) auf Statusänderungen überwacht werden. Dies geschieht durch die Funktion socket_select. Wird eine Anfrage an den ServerSocket gestellt, so wird mittels socket_accept ein neuer ClientSocket erstellt. Lesender Zugriff ist auf den ClientSockets mittels socket_recv möglich, schreibender Zugriff ist dagegen über die Funktion socket_write möglich. Listing 1 zeigt einen einfachen Server, der auf eingehende Socket-Verbindungen wartet. Listing 1: Definition des Servers in PHP
// Error Reporting und Zeitlimit für Serverbetrieb setzen
error_reporting(E_ERROR);
set_time_limit (0);

$host = 'localhost'; // Serverhost auf der gelauscht werden soll
$port = 1414; // Port auf dem Verbindungen angenommen werden sollen

// Socket erstellen
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

// Socket an Adresse und Port binden
socket_bind($sock, $host, $port);

// An Port lauschen
socket_listen($sock);

$sockets = array($sock);
$arClients = array();

while (true)
{

echo "Warte auf Verbindung...rn";

$sockets_change = $sockets;
$ready = socket_select($sockets_change, $write = null, $expect = null, null);

echo "Verbindung angenommen.rn";

foreach($sockets_change as $s)
{
if ($s == $sock)
{
// Änderung am Serversocket
$client = socket_accept($sock);
array_push($sockets, $client);
print_r($sockets);
}
else
{
// Eingehende Nachrichten der Clientsockets
$bytes = @socket_recv($s, $buffer, 2048, 0);
}
}
}

Verbindung mit dem Server auf Clientseite

Läuft der PHP-Server, so können sich Clients mittels JavaScript zum Server verbinden. Dafür ist in HTML 5 die WebSocket-Klasse vorgesehen. Dem Konstruktor dieser Klasse muss ein URI-String in der Form ws://{host}:{port}/{path_to_server} übergeben werden. Wird ein String in der Formwss:// übergeben, wird eine verschlüsselte Verbindung verwendet. Mit dem Erzeugen eines WebSocket-Objekts wird auch gleich versucht, eine Socket-Verbindung mit dem Server herzustellen. Das WebSocket-Objekt hat die Eigenschaft readyState, dieser zeigt den Status des WebSockets an.
Mögliche Statuswerte einer Socket-Verbindung Der WebSocket kann auf Clientseite vier verschiedene Statuswerte annehmen. Der Statuswert kann mittels socket.readyState abgefragt werden:
  • CONNECTING (0): Der Socket hat diesen Status beim Erstellen. Die Verbindung wurde noch nicht aufgebaut.
  • OPEN (1): Die Verbindung wurde erfolgreich aufgebaut und ein Austausch von Nachrichten ist nun möglich.
  • CLOSING (2): Die Verbindung ist dabei, beendet zu werden.
  • CLOSED (3): Die Verbindung wurde beendet oder konnte nicht aufgebaut werden.
Das WebSocket-Objekt kennt nur die beiden Methoden send und close. Die send-Funktion ermöglicht das Senden an den Server und die close-Methode ermöglicht das Beenden der Verbindung. send liegt oberhalb der Socket-Schicht und benötigt nicht die protokollspezifischen Start- und End-Bytes für das Festlegen der Frames. Meldungen, die an den Server gesendet werden sollen, können also direkt an die send-Methode übergeben werden, ohne dass die Nachricht, im Gegensatz zur PHP-Server-Implementation, spezielle Bytes enthalten muss. Es gibt vier Ereignisse, die vom WebSocket ausgelöst werden können (Kasten: “Events, die vom WebSocket auf Clientseite ausgelöst werden können”). Mittels dieser Ereignisse ist es möglich, dass der Client auf Aktionen, die vom Server ausgelöst werden, reagiert.
Events, die vom WebSocket auf Clientseite ausgelöst werden können Um auf Ereignisse reagieren zu können, ist es auf Clientseite nötig, JavaScript-Funktionen dann aufzurufen, wenn Ereignisse ausgelöst werden. Wir haben die vier möglichen Ereignisse aufgelistet, die ausgelöst werden können:
  • onopen(): Dieser Event wird nach dem Aufbauen einer Verbindung aufgerufen. Wurde es erfolgreich ausgelöst, so ist ein Senden mittels der socket.send-Methode möglich.
  • onmessage(msg): Dieser Event wird aufgerufen, wenn eine Nachricht vom Server an den verbundenen Client gesendet wird. Der Parameter, der an die aufgerufene Funktion übergeben wird, ist ein Objekt vom Typ MessageEvent. Dieser MessageEvent hat ein Attribut data, in dem die Nachricht enthalten ist.
  • onerror(error): Diese Methode wird beim Auftreten von Verbindungsabbrüchen oder sonstigen Fehlern aufgerufen.
  • onclose(): Wird die Verbindung von Server- oder Clientseite geschlossen, wird dieser Event ausgelöst.

Der Handschlag

Am Anfang ist das Array, dem socket_select übergeben wird, nur mit dem ServerSocket gefüllt. Verbindet sich nun ein Client – in unserem Fall ein HTML-5-Browser – mit diesem Socket, so wird eine Statusänderung des ServerSockets ausgelöst. Nun wird mittels socket_accept der Socket akzeptiert und dem Array hinzugefügt. Der Client sendet als Erstes einen Handschlag-Request, der vom Server beantwortet werden muss. Der Request sieht, wie in Listing 2 dargestellt, aus. Listing 2: Handshake Request des Clients an den Server
GET /test/server.php HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: localhost:1414
Origin: http://localhost
Der Client erwartet nun als Nächstes eine Antwort auf seine Verbindungsanfrage. Diese Antwort ist nötig, damit die Verbindung zwischen Client und Server zustandekommt. Die Antwort des Servers muss, wie in Listing 3 dargestellt, aussehen. Listing 3: Antwort des WebSocket Servers
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://localhost
WebSocket-Location: ws://localhost/test/server.php
War der Handschlag erfolgreich, ist die Socket-Verbindung aufgebaut und es können nun Nachrichten in beide Richtungen versendet werden. Die Socket-Verbindung hat nun auf Clientseite den Status OPEN.

Nachrichten vom Client an den Server

Die Nachrichten, die vom Clienten an den Server gesendet werden, müssen serverseitig verarbeitet beziehungsweise angenommen werden. Dies geschieht in PHP durch die Funktionsocket_select. Ändert sich nun der Status eines verbundenen Sockets, so können mittels socket_recv Daten aus dem Stream gelesen werden. Die Funktion socket_recv gibt false zurück, wenn nicht aus dem Socket gelesen werden kann, so kann man übrigens eine Trennung der Verbindung abfragen. Frames werden die Nachrichten genannt, die über die WebSockets gesendet werden. Diese Frames beginnen immer mit dem Byte 0x00 (Dezimal 0) und enden mit dem Byte 0xFF (Dezimal 255). Um die reine Nachricht zu erhalten, müssen also diese Steuerbytes abgetrennt werden. Dies kann man am einfachsten mit der Funktion substr erledigen: $message = substr($frame, 1, strlen($frame) – 2);. Damit eine Nachricht aber auf dem Server ankommt, muss sie vom Client gesendet werden. Dies geschieht über die send-Methode des WebSocket Interfaces, das in Listing 4 spezifiziert ist. Wie bereits erwähnt, müssen die Steuerbytes in JavaScript nicht angegeben werden, da die send-Methode diese automatisch an die übergebene Nachricht anfügt. Ein Beispiel für das Senden einer Nachricht sieht man in Listing 5, hier wird ein “Ich bin drin!” nach erfolgreichem Verbindungsaufbau an den Server gesendet. Listing 5: Verbindungsaufbau auf Clientseite
[Constructor(in DOMString url, optional in DOMString protocol)]
interface WebSocket {
readonly attribute DOMString URL;
// Mögliche Zustände
const unsigned short CONNECTING = 0;
const unsigned short OPEN = 1;
const unsigned short CLOSED = 2;
readonly attribute unsigned short readyState;
readonly attribute unsigned long bufferedAmount;

// Methoden
attribute Function onopen;
attribute Function onmessage;
attribute Function onclose;
boolean send(in DOMString data);
void close();
};
Listing 4: WebSocket Interface
var ws_host = "localhost";
var ws_port = "1414";
var ws_server = "test/server.php";
var ws_url = "ws://" + ws_host + ":" + ws_port + "/" + ws_server;

try
{
socket = new WebSocket(ws_url);

// Handlerfunktionen definieren
socket.onopen = function()
{
alert("Sie sind erfolgreich verbunden");

// Willkommensnachricht an den Server senden
socket.send("Ich bin drin !");
};

socket.onmessage = function(msg)
{
alert("Neue Nachricht: " + msg.data);
};

socket.onclose = function(msg)
{
alert("Verbindung wurde getrennt");
};
}
catch(ex)
{
alert("Exception: " + ex);
}

Nachrichten vom Server an den Client

Nachrichten vom Client an den Server sind mit dem klassischen HTTP(-Protokoll) ohne Weiteres möglich, mit WebSockets ist es nun aber auch möglich, Nachrichten vom Server an den Client zu senden, um dort beispielsweise Aktionen auszulösen. Um eine Nachricht an den Server zu senden, muss nun die Nachricht in ein Frame gepackt werden. Dies geschieht wieder dadurch, dass die Nachricht in die Bytes 0x00 und 0xFF eingeschlossen wird. Diese Nachricht wird dann mittels socket_write an alle oder nur an bestimmte ClientSockets gesendet. Auf Clientseite muss das WebSocket Interface implementiert sein. Das WebSocket Interface wird von der W3C wie in Listing 4 spezifiziert. Hier sieht man auch die bereits genannten Statuswerte und Methoden. Wird nun eine Nachricht vom Server an den Clienten geschickt, so wird auf Clientseite das Ereignis onmessage aufgerufen und diese bekommt die Nachricht als Parameter übergeben. Ein Beispiel für den Verbindungsaufbau und die clientseitige Nachrichtenbehandlung haben wir beispielhaft schon in Listing 5 gesehen.

Vorteile und Nachteile

Der Vorteil von WebSockets ist, dass es jetzt eine standardisierte Lösung für das Problem der bidirektionalen Kommunikation in Webanwendungen gibt. Im Vergleich zu bisherigen Lösungen bieten WebSockets eine ressourcensparende Möglichkeit für den Daten- und Informationsaustausch von Server zu Client. Ressourcensparend ist die neue Technik vor allem, weil der Protokoll-Overhead wegfällt, der bei jeder HTTP-Anfrage anfällt. Bei WebSockets bestehen die zusätzlichen Daten im Idealfall aus nur zwei Bytes pro Nachricht. Dadurch verbessert sich die Latenz und die verwendete Bandbreite wird geringer. Durch die geringere Bandbreite und bessere Latenz ist bei einer ausreichend schnellen Verbindung ein Daten- und Informationsaustausch in nahezu Echtzeit möglich. Vor allem Chatanwendungen profitieren davon, da bei vielen Clients die Verzögerung des Datenaustausches spürbar vermindert wird. Bei bisherigen bidirektionalen Lösungen können Firewalls oder Proxys Probleme bereiten, dies ist bei WebSockets nicht mehr der Fall. Comet-Lösungen umgehen die Firewalls und Proxys zwar größtenteils auch, aber eignen sich nicht für Anwendungen, die schnelle Antwortzeiten benötigen. Bisherige Socket-Implementierungen in Plug-ins wie Adobe Flash werden hingegen oft durch Proxys oder Firewalls blockiert. Dieses Problem lösen die WebSockets. Nachteilig ist, dass die WebSockets derzeit nur sehr gering auf clientseite nativ unterstützt werden, oder durch Plug-ins wie Adobe Flash emuliert werden müssen.

Ausblick

WebSockets können bisherige Comet-Lösungen ersetzen, so lassen sich z. B. Chats oder Newssysteme mit WebSockets wesentlich besser implementieren als bisher. Einsetzbar sind WebSockets auch für die Anzeige von Echtzeitinformationen, wie Börsenkurse. Sinnvoll würden sich WebSockets auch einsetzen lassen, um festzustellen, wer gerade eine Browseranwendung benutzt. Man könnte WebSockets auch gleich einsetzen, um den anderen Benutzern einer Anwendung etwas mitzuteilen, z. B. könnten bestimmte Datensätze gesperrt werden, wenn ein Benutzer an diesen Datensätzen arbeitet. Dies geht natürlich auch schon mit bisherigen Techniken, allerdings kann man mit den WebSockets auf Time-out-Mechanismen verzichten, und man weiß genauer, ob der User noch in einem bestimmten Bereich der Anwendung tätig ist. Ein weiteres großes Anwendungsgebiet sehen wir in dem immer stärker wachsenden Markt der Social Games. Bisherige Browsergames, die durch ständigen Refresh unnötig Traffic verursachen, werden wohl bald der Vergangenheit angehören.

Fazit

Es wurde Zeit, dass eine standardisierte Lösung für den bidirektionalen Datenaustausch zwischen Browser und Server gefunden wird. Mit den WebSockets setzt man auf bereits bewährte Techniken, die die Probleme und Nachteile bisheriger Lösungen beheben. Wird die vorgelegte Spezifikation rasch in die verbreiteten Browser implementiert, so kann man davon ausgehen, dass WebSockets in Zukunft für viele Anwendungen interessant werden. Gerade im Bereich der Social Networks kann man sich viele sinnvolle Anwendungen einfallen lassen, und auch für Businessanwendungen lassen sich durchaus Szenarien finden, in denen sich WebSockets als Lösung oder Optimierung anbieten. Bei wem nun das Interesse an WebSockets geweckt wurde, der kann mit dem auf der CD beigefügten Chatserver herumexperimentieren. Unter Implementierung eines PHP WebSocket Servers findet man eine PHP-Implementierung eines WebSocket Servers, der als Ausgangspunkt für komplexere Anwendungen genutzt werden kann.

Redaktion

Autor

Redaktion

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag