Przedstawiamy WebSockets – wdrażamy gniazda w internecie

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

Internet opiera się w dużej mierze na tak zwanym modelu żą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. pliki HTML). Dość dobrze działają długie ankiety i inne techniki. Używasz ich codziennie w aplikacjach takich jak czat w Gmailu.

Wszystkie te rozwiązania mają jednak 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. Pomyśl o wieloosobowych strzelankach FPP, które działają w przeglądarce lub w dowolnej innej grze online z komponentem czasu rzeczywistego.

Przedstawiamy WebSocket: gniazda internetowe w internecie

Specyfikacja WebSocket definiuje interfejs API ustanawiający połączenia „gniazda” między przeglądarką a serwerem. Innymi słowy: 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. Dostępne jest też wss: do bezpiecznego połączenia WebSocket w taki sam sposób, w jaki https: jest używany do bezpiecznych połączeń HTTP.

Przypięcie niektórych obciążników zdarzeń bezpośrednio do połączenia pozwala dowiedzieć się, kiedy połączenie zostało otwarte, kiedy otrzymano 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 powinien reprezentować nazwę protokołu podrzędnego, a serwer akceptuje tylko jeden z przekazanych protokołów podrzędnych w tablicy. Zaakceptowany protokół podrzędny 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 1 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 wysył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 wysyłać nam wiadomości w dowolnym momencie. 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 ramach najnowszej specyfikacji. Ramki binarne mogą być odbierane w formacie Blob lub ArrayBuffer. Aby określić format odbieranego pliku binarnego, ustaw właściwość binarnej 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ą nowo dodaną funkcją WebSocket są rozszerzenia. Dzięki rozszerzeniom możliwe będzie wysyłanie ramek skompresowanych, multipleksowanych itp. Aby dowiedzieć się, które rozszerzenia są akceptowane przez serwer, sprawdź właściwość extensions obiektu WebSocket po zdarzeniu open. 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ół, który umożliwia komunikację z innymi źródłami bezpośrednio w WebSocket. 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 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 korzysta z systemu uaktualniania HTTP (który jest zwykle używany w przypadku HTTP/SSL) do „uaktualniania” połączenia HTTP do połączenia 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. Ze względu na dodatkowe żądanie HTTP zawsze będą występować większe narzuty w porównaniu z czystym żądaniem WebSocket.

Po stronie serwera

Użycie WebSocket tworzy zupełnie nowy wzorzec użytkowania aplikacji po stronie serwera. Tradycyjne stosy serwerów, takie jak LAMP, zostały zaprojektowane zgodnie z cyklem żądań/odpowiedzi HTTP, ale często nie radzą sobie dobrze z dużą liczbą otwartych połączeń WebSocket. Utrzymywanie dużej liczby połączeń jednocześnie wymaga architektury, która zapewnia wysoką współbieżność przy niskim koszcie wydajności. Tego typu architektury są zwykle projektowane z użyciem wątków, czyli tzw. nieblokujące operacje wejścia-wyjścia.

Implementacje po stronie serwera

Wersje protokołu

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. Przeglądarka Firefox jest też zgodna z wersją 11, a Internet Explorer w wersji 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 z informacjami o sportowych wydarzeniach na żywo
  • strumienie z mediów społecznościowych aktualizowane w czasie rzeczywistym;

Prezentacje

Pliki referencyjne