Przedstawiamy WebSockets – gniazda w internecie

Problem: połączenia klient-serwer i serwer-klient o niskim opóźnieniu

Sieć została w dużej mierze zbudowana wokół tzw. paradygmatu żądania/odpowiedzi protokołu HTTP. Klient wczytuje stronę internetową, a potem nic się nie dzieje, dopóki użytkownik nie kliknie linku do następnej strony. Około 2005 r. technologia AJAX zaczęła sprawiać, że internet stał się bardziej dynamiczny. Jednak cała komunikacja HTTP była sterowana przez klienta, co wymagało interakcji użytkownika lub okresowego odpytywania w celu wczytania nowych danych z serwera.

Technologie, które umożliwiają serwerowi wysyłanie danych do klienta w momencie, gdy wie, że dostępne są nowe dane, istnieją już od dłuższego czasu. Są one nazywane „Push” lub „Comet”. Jednym z najczęstszych sposobów na stworzenie iluzji połączenia zainicjowanego przez serwer jest tzw. długie sondowanie. W przypadku długiego sondowania klient otwiera połączenie HTTP z serwerem, który utrzymuje je otwarte do momentu wysłania odpowiedzi. Gdy serwer ma nowe dane, wysyła odpowiedź (inne techniki obejmują Flash, żądania XHR multipart i tzw. htmlfiles). Długie sondowanie i inne techniki działają całkiem dobrze. Używasz ich codziennie w aplikacjach takich jak czat w Gmailu.

Jednak wszystkie te obejścia mają jeden problem: niosą ze sobą narzut HTTP, co sprawia, że nie nadają się do aplikacji o niskim opóźnieniu. Pomyśl o grach wieloosobowych typu strzelanka pierwszoosobowa w przeglądarce lub o innych grach online z komponentem czasu rzeczywistego.

Introducing WebSocket: Bringing sockets to the web

Specyfikacja WebSocket definiuje interfejs API, który ustanawia połączenia „gniazda” między przeglądarką internetową a serwerem. Mówiąc prościej: między klientem a serwerem istnieje stałe połączenie i obie strony mogą w każdej chwili rozpocząć wysyłanie danych.

Pierwsze kroki

Połączenie WebSocket można otworzyć, wywołując konstruktor WebSocket:

var connection = new WebSocket('ws://html5rocks.websocket.org/echo', ['soap', 'xmpp']);

Zwróć uwagę na ws:. To jest nowy schemat adresów URL połączeń WebSocket. Istnieje też wss: dla bezpiecznego połączenia WebSocket, tak samo jak https: jest używane dla bezpiecznych połączeń HTTP.

Dołączenie niektórych modułów obsługi zdarzeń bezpośrednio do połączenia pozwala dowiedzieć się, kiedy połączenie zostało otwarte, kiedy odebrano wiadomości przychodzące lub kiedy wystąpił błąd.

Drugi argument akceptuje opcjonalne podprotokoły. Może to być ciąg znaków lub tablica ciągów znaków. Każdy ciąg znaków powinien reprezentować nazwę podprotokołu, a serwer akceptuje tylko jeden z podprotokołów przekazanych w tablicy. Zaakceptowany subprotokół można określić, uzyskując dostęp do właściwości protocol obiektu WebSocket.

Nazwy podprotokołów muszą być zarejestrowane w rejestrze IANA. Obecnie (stan na luty 2012 r.) zarejestrowana jest tylko jedna nazwa podprotokołu (soap).

// When the connection is open, send some data to the server
connection.onopen = function () {
connection.send('Ping'); // Send the message 'Ping' to the server
};

// Log errors
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
};

// Log messages from the server
connection.onmessage = function (e) {
console.log('Server: ' + e.data);
};

Komunikacja z serwerem

Gdy tylko nawiążemy połączenie z serwerem (po wywołaniu zdarzenia open), możemy zacząć wysyłać dane do serwera za pomocą metody send('your message') obiektu połączenia. Kiedyś obsługiwał tylko ciągi znaków, ale w najnowszej specyfikacji może też wysyłać wiadomości binarne. Aby wysłać dane binarne, możesz użyć obiektu Blob lub ArrayBuffer.

// Sending String
connection.send('your message');

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
binary[i] = img.data[i];
}
connection.send(binary.buffer);

// Sending file as Blob
var file = document.querySelector('input[type="file"]').files[0];
connection.send(file);

Serwer może też wysyłać nam wiadomości w dowolnym momencie. W takim przypadku wywoływana jest funkcja onmessage. Wywołanie zwrotne otrzymuje obiekt zdarzenia, a rzeczywista wiadomość jest dostępna we właściwości data.

W najnowszej specyfikacji WebSocket może też odbierać wiadomości binarne. Ramki binarne mogą być odbierane w formacie Blob lub ArrayBuffer. Aby określić format otrzymanego pliku binarnego, ustaw właściwość binaryType obiektu WebSocket na „blob” lub „arraybuffer”. Domyślny format to „blob”. (Nie musisz dopasowywać parametru binaryType podczas wysyłania).

// Setting binaryType to accept received binary as either 'blob' or 'arraybuffer'
connection.binaryType = 'arraybuffer';
connection.onmessage = function(e) {
console.log(e.data.byteLength); // ArrayBuffer object if binary
};

Kolejną nową funkcją WebSocket są rozszerzenia. Dzięki rozszerzeniom można wysyłać ramki skompresowane, zmultipleksowane itp. Rozszerzenia akceptowane przez serwer można znaleźć, sprawdzając właściwość extensions obiektu WebSocket po zdarzeniu otwarcia. Do lutego 2012 r. nie opublikowano oficjalnej specyfikacji rozszerzeń.

// Determining accepted extensions
console.log(connection.extensions);

Komunikacja między różnymi domenami

Jako nowoczesny protokół, komunikacja między różnymi źródłami jest wbudowana bezpośrednio w WebSocket. Nadal należy dbać o to, aby komunikować się tylko z zaufanymi klientami i serwerami, ale WebSocket umożliwia komunikację między stronami w dowolnej domenie. Serwer decyduje, czy udostępnić usługę wszystkim klientom, czy tylko tym, którzy znajdują się w zbiorze dobrze zdefiniowanych domen.

Serwery proxy

Każda nowa technologia wiąże się z nowymi problemami. W przypadku WebSocket jest to zgodność z serwerami proxy, które pośredniczą w połączeniach HTTP w większości sieci firmowych. Protokół WebSocket używa systemu uaktualniania HTTP (który jest zwykle używany w przypadku HTTP/SSL) do „uaktualnienia” połączenia HTTP do połączenia WebSocket. Niektóre serwery proxy nie obsługują tej funkcji i zrywają połączenie. Dlatego nawet jeśli dany klient używa protokołu WebSocket, nawiązanie połączenia może nie być możliwe. Dlatego następna sekcja jest jeszcze ważniejsza :)

Korzystanie z WebSockets obecnie

WebSocket to wciąż młoda technologia, która nie została w pełni zaimplementowana we wszystkich przeglądarkach. Możesz jednak używać protokołu WebSocket z bibliotekami, które korzystają z jednego z wymienionych wyżej rozwiązań zastępczych, gdy protokół WebSocket jest niedostępny. Bardzo popularną biblioteką w tej dziedzinie jest socket.io, która zawiera implementację protokołu po stronie klienta i serwera oraz obejmuje kreacje zastępcze (w lutym 2012 r. biblioteka socket.io nie obsługiwała jeszcze wiadomości binarnych). Dostępne są też rozwiązania komercyjne, takie jak PusherApp, które można łatwo zintegrować z dowolnym środowiskiem internetowym, udostępniając interfejs HTTP API do wysyłania wiadomości WebSocket do klientów. Ze względu na dodatkowe żądanie HTTP zawsze będzie występować dodatkowe obciążenie w porównaniu z samym protokołem WebSocket.

Po stronie serwera

Korzystanie z protokołu WebSocket tworzy zupełnie nowy wzorzec użytkowania aplikacji po stronie serwera. Tradycyjne stosy serwerów, takie jak LAMP, są zaprojektowane pod kątem cyklu żądanie HTTP/odpowiedź, ale często nie radzą sobie z dużą liczbą otwartych połączeń WebSocket. Utrzymywanie dużej liczby otwartych połączeń jednocześnie wymaga architektury, która zapewnia wysoką równoczesność przy niskich kosztach wydajności. Takie architektury są zwykle projektowane wokół wątków lub tzw. nieblokujących operacji wejścia/wyjścia.

Implementacje po stronie serwera

Wersje protokołu

Protokół sieciowy (uzgadnianie połączenia i przesyłanie danych między klientem a serwerem) dla WebSocket to teraz RFC6455. Najnowsze wersje Chrome i Chrome na Androida są w pełni zgodne z RFC6455, w tym z wiadomościami binarnymi. Firefox będzie zgodny z wersją 11, a Internet Explorer z wersją 10. Nadal możesz używać starszych wersji protokołu, ale nie jest to zalecane, ponieważ są one podatne na zagrożenia. Jeśli masz implementacje serwera dla starszych wersji protokołu WebSocket, zalecamy uaktualnienie go do najnowszej wersji.

Przypadki użycia

Używaj protokołu WebSocket, gdy potrzebujesz połączenia między klientem a serwerem o bardzo niskim opóźnieniu, zbliżonym do czasu rzeczywistego. Pamiętaj, że może to wymagać ponownego przemyślenia sposobu tworzenia aplikacji po stronie serwera, z nowym naciskiem na technologie takie jak kolejki zdarzeń. Przykładowe przypadki użycia:

  • Gry online dla wielu graczy
  • Aplikacje do czatu
  • Pasek z informacjami o wydarzeniach sportowych na żywo
  • Aktualizowane w czasie rzeczywistym strumienie społecznościowe

Prezentacje

Odniesienia