, , , ,

WebSockets: Technischer Einblick und Performance-Vergleich

Nicole Wölfel

In diesem Artikel wird ein kurzer Blick auf die zu Grunde liegende, technische Funktionsweise von WebSockets geworfen und ihre Performance im Vergleich zu HTTP und Server-sent Events (SSE) untersucht, wodurch letztlich das Potenzial von WebSockets eruiert werden kann.

Hintergrund & Entstehung

In der Vergangenheit war es bei Webanwendungen nur unter unsachgemäßer Verwendung des Hypertext Transfer Protokolls (HTTP) realisierbar, eine bidirektionale Verbindung zwischen Client und Server herzustellen. Dabei sind folgende Probleme entstanden: (1.) Der Server muss verschiedene Transmission Control Protocol (TCP) Verbindungen für jeden Client nutzen. Eine, um Informationen zum Client zu senden, und eine neue für jede eintreffende Nachricht. (2.) Es gibt einen großen Overhead, da jede Client-to-Server Nachricht einen eigenen HTTP-Header hat. (3.) Das Client-seitige Skript muss sich merken, welche abgehenden Verbindungen zu welchen eingehenden Verbindungen gehören. Nur so können Antworten verfolgt werden. [vgl. 1]

Eine bessere Lösung wäre eine einzige TCP-Verbindung für beide Richtungen – hier kommt der WebSocket-Standard ins Spiel. Der Standard setzt sich zusammen aus dem WebSocket-Protokoll und der WebSocket-API (WSAPI). Der WebSocket-Standard kann einen bidirektionalen Kommunikationskanal zwischen einer Webanwendung und einem Server herstellen. Es stellt dadurch eine Alternative zu allen bislang verwendeten, HTTP-basierten Kommunikationstechnologien dar. [vgl. 1]

Im Jahr 2009 hat Google das WebSocket-Protokoll der Internet Engineering Task Force (IETF) als Draft vorgelegt [vgl. 2]. Die IETF ist ein weltweites Komitee, welches die Standardbetriebsprotokolle für das Internet definiert [vgl. 3]. Bis Mai 2010 [vgl. 4] wurde das Protokoll von Google und anderen weiterentwickelt und kontinuierlich verbessert. Nach einem zweimonatigen Stillstand hat die „BiDirectional or Server-Initiated HTTP (HyBi)“-Arbeitsgruppe – eine Gruppe der IETF – die Weiterentwicklung offiziell fortgeführt. Im Dezember 2011 ist das WebSocket-Protokoll schließlich unter dem Request for Comments (RFC) 6455 erschienen [vgl. 1]. Ein RFC ist die Form, in welcher die IETF die Standardbetriebsprotokolle formuliert [vgl. 3].

Technische Funktionsweise

Opening-Handshake

Sobald im Browser eine WebSocket-Verbindung aufgebaut wird, findet durch den sogenannten Opening-Handshake zunächst der Verbindungsaufbau statt. HTTP-basierend wird dabei ein WebSocket-Kanal eröffnet. Ein beispielhafter HTTP-GET-Request ist nachfolgend zu sehen. [vgl. 5, S. 35]

GET /chat HTTP/1.1<br>Host: server.example.com<br>Upgrade: websocket<br>Connection: Upgrade<br>Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==<br>Origin: http://example.com<br>Sec-WebSocket-Protocol: chat<br>Sec-WebSocket-Version: 13

GET /chat spricht den Endpunkt des WebSocket-Servers an. Ein Server kann dabei mehrere WebSocket-Endpoints besitzen. Mit Upgrade: websocket wird mitgeteilt, dass auf das Websocket-Protokoll umgestellt werden soll. Der eigentliche Wechsel passiert durch Connection: Upgrade. Im Header Sec-WebSocket-Key steht eine Zufallszahl. Mit ihr wird überprüft, ob der Server das WebSocket-Protokoll unterstützt. Über den Origin kann der Client den Server darüber informieren, von welcher Ursprungsdomain der Request gesendet wurde. Darauf basierend kann der Server entscheiden, ob er die Verbindung annimmt. Über den Header Sec-WebSocket-Protocol kann der Client dem Server optional mitteilen, welche Subprotokolle über den WebSocket-Kanal verarbeitet werden können. Die Versionsnummer des WebSocket-Protokolls wird mit Sec-WebSocket-Version angegeben. Da die Reihenfolge der Header vom Client zufällig gewählt werden, spielt sie keine Rolle. Wenn ein Handshake-Request nicht alle notwendigen Felder liefert, wird keine Verbindung aufgebaut. [vgl. 5, S. 36 f.]

Wird die Handshake-Anfrage vom Server akzeptiert, sendet er eine HTTP-Response an den Client [vgl. 5, S. 37]. Eine beispielhafte Response ist nachfolgend zu sehen.

HTTP/1.1 101 Switching Protocols<br>Upgrade: websocket<br>Connection: Upgrade<br>Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+0o=<br>Sec-WebSocket-Protocol: chat

Diese Antwort enthält den Statuscode 101 Switching Protocols, wodurch bestätigt wird, dass der Protokollwechsel stattfinden kann. Der Wert für Sec-WebSocket-Accept wird folgendermaßen erstellt: Zunächst wird ein Globally Unique Identifier (hier: 258EAFA5-E914-47DA-95CA-C5AB0DC85B11) and den Sec-WebSocket-Key des Handshake-Requests angehängt. Dadurch ergibt sich dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11. Aus dieser Zeichenkette wird unter Einsatz der kryptografischen Hashfunktion SHA-1 ein Hashwert erzeugt, welcher daraufhin mittels einer Base64-Codierung [vgl. 6] in einen Textstring umgewandelt wird. Dieser String ergibt den Wert des Headers Sec-WebSocket-Accept. Nun ist es dem Client möglich, anhand des Sec-WebSocket-Keys und des Sec-WebSocket-Accepts zu überprüfen, ob die Antwort von dem richtigen WebSocket-Server kommt. Sofern in dem Opening-Handshake-Request Subprotokolle im Header Sec-WebSocket-Protocol angegeben wurden, teilt der Server im gleichnamigen Header der Response mit, welche Subprotokolle er unterstützt – hier: chat. Es finden Verbindungsabbrüche statt, wenn angeforderte Subprotokolle nicht unterstützt werden oder wenn der Server mit einem nicht-angefragten Subprotokoll antwortet. Nach erfolgreichem Ablauf aller genannten Schritte gilt der Handshake als abgeschlossen und der WebSocket-Kanal wurde aufgebaut. Über diesen Kanal können nun sequenzweise Daten in Form von sogenannten Frames in beide Richtungen geschickt werden. [vgl. 5, S. 37-39]

Datentransfer

Die zu transportierenden Daten liegen als Sequenz von Frames vor. Die Protokollnachricht besteht – wie üblich – aus einem Header mit Kontrolldaten und den Nutzdaten im Payload. Ein vereinfachter Aufbau eines WebSocket-Frames ist in Abbildung 1 dargestellt. Aus dieser Darstellung ist auch abzulesen, wie schlank das WebSocket-Protokoll ist – im Vergleich zu HTTP. Der kleinste WebSocket-Frame, der vom Server zum Client gesendet wird, beträgt 2 Bytes. Ist die Header-Länge maximal, so beträgt sie 14 Bytes. [vgl. 5, S. 39]

Abbildung 1: Vereinfachter Aufbau eines WebSocket-Frames [vgl. 5, S. 39]

WebSockets übertragen Nutzdaten sequenzweise. Besondere Bedeutung hat dies, wenn Nutzdaten zum Übertragungszeitpunkt nicht vollständig sind. Sie können daher weder in ein WebSocket-Frame eingefügt werden, noch kann die Datenlänge der Nutzdaten im Frame-Header angegeben werden. Dennoch besteht die Möglichkeit, in diesem Fall Daten zu übertragen – nämlich stückchenweise. Das FIN-Bit des ersten Bytes gibt Auskunft darüber, ob der Frame vollständig ist. Trifft dies zu, so ist das Bit auf 1 gesetzt, anderenfalls auf 0. Das FIN-Bit ist in Abbildung 1 zu erkennen. [vgl. 5, S. 42 f.]

Für alle Frames, die vom Client zum Server geschickt werden, gibt es aus Sicherheitsgründen eine Besonderheit: Sie müssen maskiert werden. Für jeden Frame muss der Client dazu eine neue, noch unverwendete 32-Bit-Zufallszahl generieren. Dieser sogenannte Masking-Key wird in das entsprechende Header-Feld gesetzt (siehe Abbildung 1). Mittels eines Algorithmus werden die Nutzdaten unter Verwendung des Masking-Keys maskiert. Das Demaskieren erfolgt auf Serverseite entsprechend umgekehrt. [vgl. 5, S. 43–45]

Closing-Handshake

Ein Closing-Handshake kann sowohl vom Client als auch vom Server veranlasst werden und wird durch einem sogenannten Close-Frame gestartet. Haben sowohl Client als auch Server einen solchen Close-Frame empfangen und gesendet, gilt der Closing-Handshake als abgeschlossen. Der darunterliegende TCP-Kanal wird vom Server beendet. [vgl. 5, S. 53 f.]

Performance-Vergleich

HTTP vs. WebSockets

In einem Blogpost auf Feathersjs.com vergleicht David Luecke die Performance von HTTP und WebSockets. Der Test-Server ist dabei auf Heroku gehostet und gibt auf einen Request ein JSON-Objekt zurück. Über eine Webseite können eine festgelegte Anzahl von Requests durchgeführt werden, während die dafür benötigte Zeit gestoppt wird. [vgl. 7]

Abbildung 2: Vergleich der benötigten Zeit für einen Request zwischen HTTP und WebSocket [vgl. 7]

Die durchschnittliche Dauer eines einzigen HTTP-Requests beträgt 107 Millisekunden, während ein einzelner WebSocket-Request 83 Millisekunden dauert (siehe Abbildung 2). Für alle durchgeführten Tests wird die Zeit, die benötigt wird, um die WebSocket-Verbindung herzustellen, nicht berücksichtigt. Größere Unterschiede zeigen sich, sobald mehrere parallele Requests durchgeführt werden: 50 WebSocket-Requests dauern 180 Millisekunden, während für dieselbe Anzahl über HTTP 5 Sekunden benötigt werden (siehe Abbildung 3). Über HTTP können ungefähr 10 Requests pro Sekunden geschickt werden – bei WebSocket sind es knapp 4.000 Requests. Hauptgrund hierfür ist, dass der Browser die Anzahl der gleichzeitigen HTTP-Verbindungen begrenzt. Bei Google Chrome – dem Testbrowser – liegt die Anzahl standardmäßig bei 6 Requests. Bei WebSockets gibt es solche Beschränkungen hingegen nicht. [vgl. 7]

Abbildung 3: Vergleich der benötigten Zeit für 50 parallele Requests zwischen HTTP und WebSocket [vgl. 7]

David Luecke hat auch die übertragenen Datenmengen verglichen. Auch hier wird der initiale WebSocket-Verbindungsaufbau nicht berücksichtigt. Ein HTTP-Request und -Response erzielt eine Übertragungsmenge von 282 Bytes. Unter Verwendung eines WebSockets liegt die entsprechende Datenmenge bei 54 Bytes (31 Bytes für den Request und 24 Bytes für die Response). Zu beachten ist jedoch, dass diese Differenz mit steigender Nutzdatenmenge (Payload) abnimmt, da die Header-Größe bei HTTP gleichbleibend ist. [vgl. 7]

Abbildung 4: Vergleich der bei einem Request übertragenen Datenmenge zwischen HTTP und WebSocket [vgl. 7]

Abschließend hat David Luecke Benchmark-Tests (Lasttests) durchgeführt, um herauszufinden, ob und wie sich die bisherigen Ergebnisse verändern, wenn mehrere Clients mit dem Server kommunizieren. Die Benchmarks laufen mit 100 gleichzeitigen Verbindungen und beinhalten auch die Verbindungsaufbauzeit des WebSockets. Wenn nur ein einziger Request pro Verbindung getätigt wird, so ist HTTP etwa doppelt so schnell wie WebSocket. Bei 50 Requests pro Verbindung ist der WebSocket allerdings ca. 50 Prozent schneller. Bei einer hohen Anzahl an Requests pro Verbindung erzielen WebSockets also bessere Ergebnisse. [vgl. 7]

Server-sent Events vs. WebSockets

Bei Server-sent Events (SSE) wird ein einzelner HTTP-Request kontinuierlich aufrechterhalten, um darüber Updates zu schicken. Das bedeutet, dass Header nur einmalig – wenn der Request veranlasst wird – gesendet werden müssen. Dadurch werden für jedes Update nur die notwendigen Informationen übertragen, wodurch sich die Datenmenge reduziert. Ein weiterer Vorteil von SSE ist, dass sie sich im HTML5-Standard befinden. Das bedeutet: Bestehende Anwendungen können mit minimalem Aufwand nachträglich auf SSE umgestellt werden. Es ist jedoch zu beachten, dass SSE unidirektional funktionieren, das heißt Updates können nur vom Server zum Client gesendet werden – nicht andersherum. Eine Verbindung vom Client zum Server würde einen zusätzlichen HTTP-Request benötigen. Laut einem Test von Alexis Abril schneiden SSE im Vergleich zu WebSockets ähnlich performant ab. Die Menge der übertragenden Daten unterscheidet sich lediglich um wenige Kilobytes. Die Ergebnisse sind in Abbildung 5 dargestellt. Alexis Abril kommt zu dem Ergebnis, dass sich für die meisten Webanwendungen SSE aufgrund ihres geringeren Entwicklungsaufwands besser eignen. Falls jedoch häufig Daten vom Client zum Server geschickt werden müssen, liefern WebSockets eine bessere Performance, wodurch der etwas höhere Implementierungsaufwand gerechtfertigt werden kann. [vgl. 8]

Abbildung 5: Testergebnisse Vergleich zwischen Server-sent Events und WebSocket [vgl. 8]

Vor- und Nachteile von WebSockets

Vorteile von WebSockets sind, dass über einen bidirektionalen Kanal Daten in Echtzeit zwischen Client und Server übermittelt werden können. Dies beschränkt sich dabei nicht auf Textdateien, sondern es können auch binäre Daten (z. B. Video-, Audio- und Bilddateien) übertragen werden. Zudem haben WebSockets nur einen kleinen Overhead, d. h. es werden geringere Datenmengen übertragen, wodurch eine bessere Performance erzielt werden kann. Gerade für Anwendungen, die häufig mobil genutzt werden, kann dies entscheidend sein. [vgl. 5, S. 35]

Ebenso weisen WebSockets eine sehr gute Browserunterstützung auf. Seit 2013 werden sie von allen gängigen Desktop-Browsern unterstützt und inzwischen auch von den vertretenen Smartphone-Browsern. Welche Browser WebSockets unterstützen ist aus Abbildung 6 zu entnehmen. [vgl. 10]

Abbildung 6: Browserunterstützung von WebSockets [10]

Gravierende Nachteile weisen WebSockets nicht auf. Man sollte sich jedoch über gewisse Sicherheitsrisiken bewusst sein und entsprechende Schutzmaßnahmen einrichten. Die Risiken gleichen denen des zugrundeliegenden HTTP-Protokolls. So sind beispielsweise Denial-of-Service-Angriffe (DoS-Angriffe) möglich, da die Anzahl an Verbindungen zum Server nicht begrenzt wird. Bei einer DoS-Attacke wird eine hohe Anzahl an Anfragen an den Server gesendet, sodass das System enorm langsam wird oder sogar zusammenbricht [vgl. 11]. Zudem findet keine Authentifizierung und Autorisierung während des Handshake-Prozesses statt. Ebenso wird der Nutzer-Input nicht geprüft, wodurch z. B. Cross-Site-Scripting (XSS) und SQL-Injections möglich sind. [vgl. 12]

In [13] sind einige Best Practices einzusehen, die für einen sicheren Einsatz von WebSockets umgesetzt werden sollten.

Fazit

In WebSockets steckt großes Potenzial, da es bislang keine andere Technologie gibt, die eine bidirektionale Verbindung zwischen Client und Server ermöglicht. Auch kann eine sehr hohe Performance erzielt werden. Allerdings gibt es gewisse Sicherheitsrisiken, auf die bei der Implementierung geachtet werden sollte. Sofern Textdaten lediglich unidirektional vom Server zum Client gesendet werden müssen, empfielt sich stattdessen die Verwendung von Server-sent Events. Diese erzielen eine vergleichbare Performance, bei einem geringeren Entwicklungsaufwand.

Literatur

Comments

Leave a Reply