22.9. Веб-сокеты

We use cookies. Read the Privacy and Cookie Policy

В главе 18 демонстрируется, как клиентские сценарии на языке JavaScript могут взаимодействовать с серверами по сети. Все примеры в этой главе используют протокол HTTP, а это означает, что все они ограничены исходной природой протокола HTTP: этот протокол, не имеющий информации о состоянии, состоит из запросов клиента и ответов сервера. Протокол HTTP фактически является узкоспециализированным сетевым протоколом. Более универсальные сетевые взаимодействия через Интернет (или через локальные сети) часто реализуются с использованием долгоживущих соединений и обеспечивают двунаправленный обмен сообщениями через TCP-сокеты. Довольно небезопасно предоставлять клиентскому сценарию на языке JavaScript доступ к низкоуровневым ТСР-сокетам, однако спецификация «WebSocket АРІ» определяет безопасную альтернативу: она позволяет клиентским сценариям создавать двунаправленные соединения с серверами, поддерживающими протокол веб-сокетов. Это существенно упрощает решение некоторых сетевых задач.

Прикладной интерфейс веб-сокетов удивительно прост в использовании. Сначала необходимо создать сокет с помощью конструктора WebSocket():

var socket = new WebSocket("ws://ws.example.com:1234/resource");

Протокол веб-сокетов

Чтобы использовать веб-сокеты в сценариях на языке JavaScript, достаточно будет освоить клиентский прикладной интерфейс веб-сокетов, описываемый здесь. Не существует эквивалентного серверного прикладного интерфейса для создания серверов с поддержкой веб-сокетов; в этом разделе будет представлен простой пример сервера, основанного на использовании интерпретатора Node (раздел 12.2) и сторонней серверной библиотеки поддержки веб-сокетов. Клиент и сервер взаимодействуют через долгоживущие TCP-сокеты, следуя правилам, определяемым протоколом веб-соке-тов. Мы не будем рассматривать здесь особенности протокола, но следует отметить, что протокол веб-сокетов спроектирован очень аккуратно, благодаря чему веб-серверы могут легко обрабатывать HTTP-соединения и соединения на основе веб-сокетов через один и тот же порт.

Веб-сокеты получили широкую поддержку среди производителей броузеров. В ранней, предварительной версии протокола веб-сокетов была обнаружена серьезная брешь в системе безопасности, поэтому на момент написания этих строк в некоторых броузерах поддержка веб-сокетов была отключена, - до стандартизации безопасной версии протокола. В Firefox 4, например, может потребоваться явно включить поддержку веб-сокетов, открыв страницу about:config и установив переменную «network.websocket. override-security-block» в значение true.

*********************************************

Аргументом конструктора WebSocket() является URL-адрес, в котором используется протокол ws:// (или wss:// - в случае с безопасными соединениями, по аналогии с https://). URL-адрес определяет имя хоста, к которому выполняется подключение, и может также определять порт (по умолчанию веб-сокеты используют тот же порт, что и протоколы HTTP и HTTPS) и путь или ресурс.

После создания сокета в нем обычно регистрируются обработчики событий:

socket.onopen = function(e) { /* Соединение установлено. */ };

socket.onclose = function(e) { /* Соединение закрыто. */ };

socket.onerror = function(e) { /* Что-то пошло не так! */ };

socket.onmessage = function(e) {

  var message = e.data; /* Сервер послал сообщение. */

};

Чтобы отправить данные серверу через сокет, следует вызвать метод send() сокета:

socket.send("Привет, сервер!");

Текущая версия прикладного интерфейса веб-сокетов поддерживает только текстовые сообщения и отправляет их в виде строк в кодировке UTF-8. Однако текущая версия спецификации протокола веб-сокетов включает поддержку двоичных сообщений, и будущие версии прикладного интерфейса, возможно, будут обеспечивать обмен двоичными данными с сервером.

По окончании взаимодействия с сервером сценарий может закрыть веб-сокет вызовом его метода close().

Веб-сокеты являются двунаправленными, и единственное соединение, установленное через веб-сокет, может использоваться клиентом и сервером для передачи сообщений друг другу в любой момент времени. Это взаимодействие не обязательно должно иметь форму запросов и ответов. Каждая служба, основанная на веб-сокетах, будет определять собственный «подпротокол» передачи данных между клиентом и сервером. С течением времени эти «подпротоколы» могут развиться, и вам может потребоваться реализовать клиенты и серверы, поддерживающие несколько версий подпротокола. К счастью, протокол веб-сокетов включает механизм, дающий возможность договориться о выборе подпротокола, который поддерживается и клиентом, и сервером. Конструктору WebSocket() можно передать массив строк. Сервер получит его в виде списка подпротоколов, поддерживаемых клиентом. Сервер выберет подпротокол, поддерживаемый им, и отправит его обратно клиенту. После установления соединения клиент сможет определить, какой подпротокол можно использовать, проверив свойство protocol объекта WebSocket.

В разделе 18.3 описывается прикладной интерфейс объекта EventSource и демонстрируется его применение на примере реализации клиента и сервера чата. Вебсокеты еще больше упрощают реализацию подобных приложений. В примере 22.16 демонстрируется очень простой клиент чата: он напоминает пример 18.15, но использует веб-сокеты для двунаправленного обмена сообщениями вместо объекта EventSource для приема сообщений и XMLHttpRequest - для отправки.

Пример 22.16. Клиент чата на основе веб-сокетов

<script>

window.onload = function() {

  // Позаботиться о некоторых деталях пользовательского интерфейса

  var nick = prompt("Введите свой псевдоним"); // Получить псевдоним

  var input = document.getElementById("input"); // Отыскать поле ввода

  input.focus(); // Установить фокус ввода

  // Открыть веб-сокет для отправки и приема сообщений в чате.

  // Предполагается, что HTTP-сервер, откуда загружается сценарий, также

  // поддерживает веб-сокеты, и для связи с ним используется то же имя хоста

  // и номер порта, но вместо протокола http:// используется протокол ws://

  var socket = new WebSocket("ws://" + location.host + "/");

  // Так через веб-сокет принимаются сообщения с сервера

  socket.onmessage = function(event) { // Вызывается при получении сообщения

    var msg = event.data; // Получить текст из объекта события

    var node = document.createTextNode(msg); // Создать текстовый узел

    var div = document.createElement("div"); // Создать элемент <div>

    div.appendChild(node); // Добавить текстовый узел

    document.body.insertBefore(div, input); // и вставить div перед полем ввода

    input.scrollIntoView(); // Гарантировать видимость элемента input

  }

  // Так через веб-сокет отправляются сообщения на сервер

  input.onchange = function() { // Когда пользователь нажмет клавишу Enter

    var msg = nick + ": " + input.value; // Имя пользователя и текст

    socket.send(msg); // Отправить через сокет

    input.value = ""; // Подготовиться к вводу следующего сообщения

  }

};

</script>

<!-- Пользовательский интерфейс - это единственное поле ввода -->

<!-- Новые сообщения в чате будут появляться перед этим элементом -->

<input id="input” style="width:100%"/>

В примере 22.17 демонстрируется реализация сервера чата, основанного на веб-сокетах, которая предназначена для работы под управлением интерпретатора Node (раздел 12.2). Сравните этот пример с примером 18.17, чтобы увидеть, что веб-сокеты упрощают не только клиентскую часть реализации чата, но и серверную.

Пример 22.17 Сервер чата на основе веб-сокетов, выполняющийся под управлением Node

/*

* Этот серверный сценарий на языке JavaScript предназначен для выполнения

* под управлением NodeJS. Он играет роль сервера веб-сокетов, реализованного поверх

* HTTP-сервера с использованием внешней библиотеки websocket, которую можно найти

* по адресу: https://github.com/miksago/node-websocket-server/. При обращении

* к ресурсу "/" он возвращает HTML-файл клиента чата. В ответ на обращение к любому

* другому ресурсу возвращается код 404. Сообщения принимаются посредством протокола

* веб-сокетов и просто рассылаются по всем активным соединениям.

*/

var http = require('http');           // Использовать HTTP-сервер в Node

var ws = require('websocket-server'); // Использовать внешнюю библиотеку

// Прочитать исходный файл с реализацией клиента чата. Используется ниже,

var clientui = require('fs').readFileSync("wschatclient.html");

// Создать НТТР-сервер

var httpserver = new http.Server();

// Когда HTTP-сервер получит новый запрос, будет вызвана эта функция

httpserver.on("request”, function (request, response) {

  // Если запрошен ресурс "/", отправить реализацию клиента чата,

  if (request.иrl === "/") { // Запрошена реализация клиента чата

    response.writeHead(200, {''Content-Type”: "text/html”});

    response.write(clientui); response.end();

  }

  else { // В ответ на любой другой запрос отправить код 404 "Not Found"

    response.writeHead(404);

    response.end();

  }

});

// Обернуть HTTP-сервер сервером на основе веб-сокетов

var wsserver = ws.createServer({server: httpserver});

// Вызывать эту функцию при получении новых запросов на соединение

wsserver.on("connection", function(socket) {

  socket.send("Добро пожаловаь в чат."); // Приветствовать нового клиента

  socket.on("message", function(msg) { // Принимать сообщения от клиента

    wsserver.broadcast(msg); // И рассылать их всем остальным

  });

});

// Запустить сервер на порту 8000. Запуск сервера на основе веб-сокетов

// приводит также к запуску HTTP-сервера. Для его использования подключайтесь

// по адресу http://localhost:8000/. wsserver.listen(8000);