Przedstawiamy WebSockets – wdrażamy gniazda w internecie

Problem: połączenia klient-serwer oraz klient-klient z krótkim czasem oczekiwania

Internet opiera się w dużej mierze na tak zwanym modelu żądania/odpowiedzi HTTP. Klient wczytuje stronę internetową i nic się nie dzieje, dopóki użytkownik nie kliknie przejścia do następnej strony. Około 2005 r. technologia AJAX zaczęła sprawiać, że internet wydaje się bardziej dynamiczny. Mimo to 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 dokładnie wtedy, gdy wie, że są dostępne nowe dane, istnieją już od dłuższego czasu. Mają one takie nazwy jak „Push” lub „Komit”. Jednym z najczęstszych sposobów na stworzenie iluzji połączenia inicjowanego przez serwer jest tzw. „długie odpytywanie”. W przypadku długich odpytywania klient otwiera połączenie HTTP z serwerem, które pozostaje otwarte do momentu wysłania odpowiedzi. Gdy tylko mają nowe dane, wysyła odpowiedź (inne techniki obejmują żądania Flash, multipart i tzw. htmlfiles). Dość dobrze działają długie ankiety i inne techniki. Są one używane codziennie w aplikacjach, takich jak czat w Gmailu.

Wszystkie te sposoby obejścia problemu mają jednak jeden problem: wiążą się z dużym nakładem pracy HTTP, co nie sprawia, że dobrze sprawdzają się w aplikacjach o krótkim czasie oczekiwania. 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: dostęp do sieci

Specyfikacja WebSocket definiuje interfejs API służący do nawiązywania połączeń „socket” między przeglądarką a serwerem. Mówiąc prościej: istnieje trwałe połączenie między klientem a serwerem i obydwie strony mogą w dowolnym momencie rozpocząć wysyłanie danych.

Pierwsze kroki

Połączenie WebSocket otwiera się, wywołując konstruktor WebSocket:

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

Zwróć uwagę na ws:. To jest nowy schemat URL-i połączeń WebSocket. Dostępna jest również technologia wss: do obsługi bezpiecznego połączenia WebSocket w taki sam sposób, w jaki używa się https: do bezpiecznych połączeń HTTP.

Natychmiastowe dołączenie niektórych modułów obsługi zdarzeń do połączenia pozwala sprawdzać, kiedy połączenie zostało nawiązane, odebrano wiadomości przychodzące lub wystąpi błąd.

Drugi argument akceptuje opcjonalne protokoły podrzędne. Może to być ciąg 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.

Nazwą protokołu podrzędnego musi być jedna z nazw podprotokołów zarejestrowanych w rejestrze IANA. Obecnie tylko jedna nazwa protokołu podrzędnego (mydło) jest zarejestrowana w lutym 2012 r.

// 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

Natychmiast po połączeniu z serwerem (po wywołaniu zdarzenia open) możemy zacząć wysyłać dane do serwera przy użyciu metody send('your message') w obiekcie połączenia. Wcześniej obsługiwał on tylko ciągi tekstowe, ale według 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);

Także serwer może wysyłać nam wiadomości w każdej chwili. W takim przypadku wywoływane jest wywołanie zwrotne onmessage. Wywołanie zwrotne odbiera obiekt zdarzenia, a rzeczywisty komunikat jest dostępny przez właściwość data.

WebSocket może też odbierać komunikaty binarne w najnowszej specyfikacji. Ramki binarne można odbierać w formacie Blob lub ArrayBuffer. Aby określić format odbieranego pliku binarnego, ustaw właściwość binarnej obiektu WebSocket na „blob” lub „arraybuffer”. Domyślny format to „blob”. (Przy wysyłaniu nie musisz dopasowywać parametru binarnego typu binarnego).

// 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. Korzystając z rozszerzeń, można wysyłać klatki skompresowane, multipleksowane itd. Aby znaleźć rozszerzenia akceptowane przez serwer, należy sprawdzić właściwość rozszerzeń obiektu WebSocket po zdarzeniu otwarcia. Nie ma jeszcze oficjalnie opublikowanej specyfikacji rozszerzeń (stan na luty 2012 r.).

// 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. Mimo że nadal należy komunikować się tylko z zaufanymi klientami i serwerami, WebSocket umożliwia komunikację między podmiotami 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 firmowych sieci. Protokół WebSocket używa systemu uaktualniania HTTP (który jest zwykle używany w przypadku HTTP/SSL) do „uaktualniania” połączenia HTTP do połączenia WebSocket. Niektórym serwerom proxy to się nie podoba i przerywa połączenie. Dlatego nawet jeśli klient korzysta z protokołu WebSocket, nawiązanie połączenia może nie być możliwe. To sprawia, że następna sekcja jest jeszcze ważniejsza :)

Zacznij korzystać z WebSockets już dziś

WebSocket jest nadal młodą technologią i nie została w pełni wdrożona we wszystkich przeglądarkach. Obecnie jednak można używać WebSocket w bibliotekach korzystających z jednego z wymienionych wyżej kreacji zastępczych, gdy jest on niedostępny. Biblioteka, która stała się w tej domenie bardzo popularna, to socket.io, która zawiera klienta i serwer implementacji protokołu oraz wartości zastępcze (od lutego 2012 r. socket.io nie obsługuje jeszcze przesyłania komunikatów binarnych). Istnieją również rozwiązania komercyjne, takie jak PusherApp, które można łatwo zintegrować z dowolnym środowiskiem internetowym, udostępniając interfejs API HTTP do wysyłania wiadomości z 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. Jednocześnie otwarta duża liczba połączeń wymaga architektury, która otrzymuje wysoką równoczesność 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ół przewodu (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 ze standardem RFC6455, w tym z wiadomościami binarnymi. Przeglądarka Firefox jest też zgodna z wersją 11, a Internet Explorer w wersji 10. Możesz nadal używać starszych wersji protokołów, ale nie jest to zalecane, ponieważ są one znane jako podatne na ataki. Jeśli masz implementacje serwerowe starszych wersji protokołu WebSocket, zalecamy ich uaktualnienie do najnowszej wersji.

Przykłady zastosowań

WebSocket jest zalecany, gdy potrzebujesz naprawdę małego opóźnienia, prawie w czasie rzeczywistym połączenia między klientem a serwerem. Pamiętaj, że może to wymagać ponownego zastanowienia się nad sposobem tworzenia aplikacji po stronie serwera z ponownym skupieniem się na technologiach takich jak kolejki zdarzeń. Oto kilka przykładów:

  • Gry online wieloosobowe
  • Aplikacje do obsługi czatu
  • Pasek sportowy na żywo
  • Strumienie społecznościowe aktualizowane w czasie rzeczywistym

Przykłady

Źródła