Przedstawiamy WebSockets – wdrażamy gniazda w internecie

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

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

Technologie umożliwiające serwerowi wysyłanie danych do klienta w chwili, gdy wie, że są dostępne nowe dane, istnieją już od jakiegoś czasu. Mają one nazwy takie jak „Push” czy „Comet”. Jednym z najczęstszych sposobów na stworzenie iluzji połączenia zainicjowanego przez serwer jest długie odpytywanie. W przypadku długiego pollingu klient otwiera połączenie HTTP z serwerem i utrzymuje je otwarte do momentu wysłania odpowiedzi. Gdy serwer ma nowe dane, wysyła odpowiedź (inne techniki obejmują żądania Flash, XHR multipart i tzw. htmlfiles). Długie zapytania pollingowe i inne techniki działają całkiem nieźle. Używasz ich codziennie w aplikacjach takich jak czat w Gmailu.

Wszystkie te rozwiązania mają jednak jeden wspólny problem: mają nadmiarowe obciążenie związane z protokołem HTTP, co powoduje, że nie nadają się one do aplikacji o małej latencji. Mowa o strzelankach pierwszoosobowych dla wielu graczy w przeglądarce lub innych grach online z elementem czasu rzeczywistego.

Przedstawiamy WebSocket: gniazda internetowe w internecie

Specyfikacja WebSocket definiuje interfejs API ustanawiający połączenia „gniazda” między przeglądarką a serwerem. Mówiąc w prosty sposób: między klientem a serwerem istnieje trwałe połączenie, a obie strony mogą w dowolnym momencie rozpocząć przesyłanie danych.

Pierwsze kroki

Aby otworzyć połączenie WebSocket, wystarczy wywołać konstruktor WebSocket:

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

ws: To nowy schemat URL-a dla połączeń WebSocket. Jest też wss: do bezpiecznego połączenia WebSocket, które działa tak samo jak https: w przypadku bezpiecznych połączeń HTTP.

Przypięcie niektórych obciążników zdarzeń bezpośrednio do połączenia pozwala dowiedzieć się, kiedy zostało ono otwarte, kiedy otrzymało przychodzące wiadomości lub kiedy wystąpił błąd.

Drugi argument akceptuje opcjonalne protokoły. Może to być ciąg tekstowy lub tablica ciągów tekstowych. Każdy ciąg znaków powinien reprezentować nazwę podprotokołu, a serwer akceptuje tylko jeden z podanych w tablicy podprotokołów. Akceptowany podprotokół 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 w lutym 2012 r. zarejestrowano tylko jedną nazwę 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 (gdy zostanie wywołane zdarzenie open), możemy zacząć wysyłać dane na serwer za pomocą metody send('your message') obiektu połączenia. Wcześniej 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);

Podobnie serwer może w dowolnym momencie wysyłać do nas wiadomości. Gdy to nastąpi, zostanie wywołana funkcja onmessage. Wywołanie zwrotne otrzymuje obiekt zdarzenia, a dostęp do rzeczywistej wiadomości można uzyskać za pomocą właściwości data.

WebSocket może też odbierać wiadomości binarne w najnowszej specyfikacji. Ramki binarne mogą być odbierane w formacie Blob lub ArrayBuffer. Aby określić format otrzymanych danych binarnych, ustaw właściwość binaryType obiektu WebSocket na „blob” lub „arraybuffer”. Format domyślny 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, multipleksowane itp. Aby znaleźć rozszerzenia akceptowane przez serwer, sprawdź właściwość rozszerzeń obiektu WebSocket po zdarzeniu otwarcia. W lutym 2012 r. nie było jeszcze oficjalnie opublikowanej specyfikacji rozszerzeń.

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

Komunikacja między domenami

Jest to nowoczesny protokół, w którym komunikacja między domenami jest wbudowana. Chociaż nadal musisz mieć pewność, że komunikujesz się tylko z klientami i serwerami, którym ufasz, protokół 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 powoduje nowe problemy. 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 korzysta z systemu uaktualniania HTTP (który jest zwykle używany w przypadku HTTP/SSL) do „uaktualnienia” połączenia HTTP na połączenie WebSocket. Niektóre serwery proxy nie obsługują tego typu połączeń i przerywają je. Dlatego nawet jeśli dany klient używa protokołu WebSocket, może nie być możliwe nawiązanie połączenia. Dlatego następna sekcja jest jeszcze ważniejsza :)

Korzystanie z WebSockets

WebSocket to wciąż nowa technologia, która nie jest w pełni wdrażana we wszystkich przeglądarkach. Możesz jednak używać WebSocket w bibliotekach, które korzystają z jednego z wymienionych powyżej rozwiązań alternatywnych, gdy WebSocket jest niedostępny. Biblioteka, która stała się bardzo popularna w tej dziedzinie, to socket.io, która zawiera klienta i serwer z implementacją protokołu oraz zawiera alternatywne rozwiązania (socket.io nie obsługuje jeszcze wiadomości binarnych od lutego 2012 r.). Istnieją 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. Z powodu dodatkowego żądania HTTP zawsze będzie dodatkowy narzut w porównaniu z czystym WebSocket.

Po stronie serwera

Korzystanie z WebSocket tworzy zupełnie nowy wzorzec użytkowania aplikacji po stronie serwera. Chociaż tradycyjne zestawy serwerów, takie jak LAMP, są zaprojektowane pod kątem cyklu żądania i odpowiedzi HTTP, często nie radzą sobie z dużą liczbą otwartych połączeń WebSocket. Utrzymywanie dużej liczby połączeń naraz wymaga architektury, która zapewnia wysoką współbieżność przy niskim koszcie wydajności. Takie architektury są zwykle projektowane z wykorzystaniem wątków lub tak zwanego nieblokującego IO.

Implementacje po stronie serwera

Wersje protokołów

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

Przypadki użycia

Używaj WebSocket, gdy potrzebujesz połączenia o bardzo niskich opóźnieniach, niemal w czasie rzeczywistym, między klientem a serwerem. Pamiętaj, że może to wymagać zmiany sposobu tworzenia aplikacji po stronie serwera z nową uwagą na technologie takie jak kolejki zdarzeń. Oto kilka przykładowych zastosowań:

  • Gry wieloosobowe online
  • Aplikacje do obsługi czatu
  • Pasek aktywności na żywo
  • Aktualizowanie strumieni mediów społecznościowych w czasie rzeczywistym

Prezentacje

Odniesienia