Руководство пользователя веб-сокета

JavaScript WebSocket
Руководство пользователя веб-сокета

предисловие

При общении между браузером и сервером традиционный HTTP-запрос не идеален в некоторых сценариях, таких как чат в реальном времени, мини-игры в реальном времени и т. д. Он сталкивается с двумя основными недостатками:

  • Невозможно добиться «реального времени» новостей;
  • Сервер не может активно передавать информацию;

Его основные решения на основе HTTP:

  • Опрос на основе Ajax: клиент постоянно запрашивает интерфейс к серверу через короткие промежутки времени или динамически через короткие промежутки времени, и спрашивает сервер, есть ли новая информация, его недостатки также очевидны: избыточные пустые запросы (трата ресурсов впустую), задержки получения данных ;
  • Длинный опрос: используется схема блокировки, клиент инициирует запрос Ajax к серверу, сервер приостанавливает запрос и не возвращает данные до тех пор, пока не появятся новые данные, а клиент снова выполняет длинный опрос после получения данных; в этой схеме Каждый запрос приостанавливает ресурсы сервера, что недопустимо в сценариях с большим количеством подключений;

Видно, что все решения на основе протокола HTTP содержат существенный недостаток - "пассивность", сервер не может проталкивать сообщения, и только клиент может инициировать запросы, чтобы постоянно спрашивать, есть ли новые сообщения. .

WebSocket — это сетевая технология, предоставляемая HTML5 для полнодуплексной связи между браузерами и серверами. Протокол связи WebSocket был установлен IETF в качестве стандарта RFC 6455 в 2011 году, а API WebSocket был установлен W3C в качестве стандарта. В WebSocket API браузеру и серверу достаточно сделать рукопожатие, после чего между браузером и сервером формируется быстрый канал. Данные могут передаваться напрямую между ними.

WebSocket — это новый стандарт сетевого протокола, предложенный в HTML5, который включает в себя несколько функций:

  • Прикладной уровень, построенный поверх протокола TCP;
  • Как только соединение установлено (до момента отключения или ошибки), сервер и клиент сохранят состояние соединения после рукопожатия, которое является постоянным соединением;
  • Сервер может активно отправлять сообщения по каналу реального времени;
  • «Реальный (относительный)» и «последовательный» прием данных;

упражняться

Использовать веб-сокеты в браузерах очень просто, с роднымWebSocektОбъект, в котором прием сообщения и обработка фрейма данных инкапсулированы в браузере.
Далее на простом примере объясняется, как использоватьWebSocekt;

Реализация сервера

Конечно, использование Websocket требует, чтобы сервер предоставлял возможность предоставлять клиенту, здесь на основе Node.js и WS просто создать серверный интерфейс WebSocket:

const express = require('express');
const WebSocket = require('ws');
const http = require('http');

const app = express();

app.get('/', function (req, res) {
  res.sendfile('./index.html');
});

const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

wss.on('connection', function connection(ws, req) {
  ws.on('message', function incoming(message) {
    ws.send('received: ' + message + '(From Server)');
  });

  ws.send('Hello Client');
});

server.listen(8080, function listening() {
  console.log('Listening on %d', server.address().port);
});

Экспресс-запрос и запрос Websocket отслеживаются на порту 8080, поскольку их собственные протоколы (http(s):// и ws(s)://) отличаются, поэтому они не будут конфликтовать.
При этом в коде видно, что он сначала слушаетconnectionсобытие (вызванное установлением соединения), прослушивание его обратного вызоваmessageсобытие (полученное сообщение) и немедленноsendчасть данных.

реализация браузера

Websocket API

Нативные классы доступны в браузереWebSocket,использоватьnewключевое слово для его создания:

WebSocket WebSocket(String url,optional String | [] protocols);

Принимает два параметра:

  • url указывает адрес для подключения, например: ws://localhost:8080;
  • протоколы — необязательный параметр, который может быть строкой или массивом, который используется для представления подпротокола, что позволяет серверу реализовать несколько подпротоколов WebSocket;

Создание экземпляра объекта предоставляет два метода:

  • send получает данные String|ArrayBuffer|Blob и отправляет их на сервер в виде данных;
  • close получает (необязательный) код (номер состояния закрытия, значение по умолчанию — 1000) и (необязательную) строку (представляющую причину отключения), и клиент активно отключается;

Состояние подключения:

  • Класс WebSocket предоставляет некоторые константы для указания статуса соединения:
    • WebSocket.CONNECTING 0 Соединение не открыто;
    • WebSocket.OPEN 1 Соединение открыто и готово к обмену данными;
    • WebSocket.CLOSING 3 Соединение находится в процессе закрытия;
    • WebSocket.CLOSED 4 Соединение было закрыто или соединение не может быть установлено;
  • Свойство readyState предоставляется в экземпляре объекта WebSocket для оценки текущего состояния;

В созданном объекте можно прослушивать следующие события:

  • событие open Callback для открытия соединения, readyState становится OPEN;
  • сообщение получает событие обратного вызова сообщения, а функция обратного вызова получает данные MessageEvent;
  • close Событие обратного вызова закрытия соединения, затем readyState становится CLOSED;
  • error Событие обратного вызова ошибки, произошедшей в процессе установления и соединения;

Код

const ws = new WebSocket('ws://localhost:8080');

let sendTimmer = null;
let sendCount = 0;

ws.onopen = function () {
  console.log('@open');

  sendCount++;
  ws.send('Hello Server!' + sendCount);

  sendTimmer = setInterval(function () {
    sendCount++;
    ws.send('Hi Server!' + sendCount);

    if (sendCount === 10) {
      ws.close();
    }
  }, 2000);
};
ws.onmessage = function (e) {
  console.log('@message');
  console.log(e.data);
};
ws.onclose = function () {
  console.log('@close');
  sendTimmer && clearInterval(sendTimmer);
};
ws.onerror = function () {
  console.log('@error');
};

Вы можете увидеть в консоли:

@open
@message
Hello Client
@message
received: Hello Server!1(From Server)
@message
received: Hi Server!2(From Server)
@message
received: Hi Server!3(From Server)
@message
received: Hi Server!4(From Server)
@message
received: Hi Server!5(From Server)
@message
received: Hi Server!6(From Server)
@message
received: Hi Server!7(From Server)
@message
received: Hi Server!8(From Server)
@message
received: Hi Server!9(From Server)
@close

Сначала запускается событие открытия, а затем каждый раз, когда данные отправляются, сервер будет отвечать данными, поэтому запускается событие сообщения.После отправки 10 раз браузер активно отключается, поэтому запускается событие закрытия; здесь нет ответа от сервера было получено после последней передачи.Также потому что клиент сразу отключился;
Конечно, из сети можно увидеть более конкретное взаимодействие данных;

события и данные

Есть два способа отслеживать события в экземпляре WebSocket.В качестве примера мы возьмем событие сообщения:

  • Назначьте свойство onmessage напрямую, как указано выше:ws.onmessage = function () {};;
  • Используйте addEventListener для прослушивания таких событий, как:ws.addEventListener('message', function () {});

Получить параметр типа MessageEvent e в функции обратного вызова сообщения, а нужные нам данные можно получить через e.data;
Следует отметить, что и сервер, и клиент, данные, которые они получают, представляют собой сериализованные строки (конечно, есть также данные типа ArrayBuffer|Blob).Много раз нам нужно анализировать и обрабатывать данные, напримерJSON.parse(e.data);

стабильность соединения

Из-за сложной сетевой среды в некоторых случаях может произойти отключение или ошибка подключения, нам необходимо отслеживать ненормальное отключение и повторное подключение в случае закрытия или ошибки;

По какой-то причине, когда ошибка браузера и не отвечает на событие обратного вызова, поэтому ошибка о необходимости открыть заправленную задачу после открытия, чтобы определить текущее состояние подключенияreadyState, попытаться переподключиться в случае исключения;

стук сердца

Спецификация websocket определяет механизм сердцебиения, одна сторона может отправить сообщение ping (код операции 0x9) другой стороне, а другая сторона должна вернуть pong (0xA) как можно скорее после получения ping.

Механизм пульса используется для определения онлайн-статуса подключенного контрагента, поэтому, если пульса нет, невозможно определить, что одна сторона все еще подключена, некоторые сетевые уровни, такие как nginx или слои браузера, будут активно отключаться.

В JavaScript WebSocket не открывает API ping/pong.Хотя в браузере есть собственная обработка сердцебиения, реализация у разных производителей не одинакова, поэтому нам нужно согласовать с сервером, когда мы разрабатываем самореализуемый механизм сердцебиения;
Например, в браузере после обнаружения события open запускается задача по расписанию, и каждый раз отправляются данные0x9На сервер, и сервер возвращается0xAкак ответ;
На практике задача синхронизации сердцебиения обычно отправляется каждые 15-20 секунд.


Сетевой протокол

Как упоминалось ранее, Websocket построен поверх TCP, так какое же он имеет отношение к протоколу HTTP?

Соединение Websocket делится на фазу установления соединения и фазу соединения.На фазе установления соединения используется HTTP, а на фазе соединения оно не зависит от HTTP.

стадия соединения

В сети браузера найдите соединение ws, вы можете увидеть:

General
Request URL:ws://localhost:8080/
Request Method:GET
Status Code:101 Switching Protocols

Response Headers
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: py9bt3HbjicUUmFWJfI0nhGombo=

Request Headers
GET ws://localhost:8080/ HTTP/1.1
Host: localhost:8080
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://localhost:8080
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36
DNT: 1
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7,la;q=0.6,ja;q=0.5
Sec-WebSocket-Key: 2idFk3+96Hs5hh+c9GOQCg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

Это стандартный HTTP-запрос, по сравнению с нашим распространенным протоколом HTTP-запроса в заголовке запроса есть несколько дополнительных полей:

Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: 2idFk3+96Hs5hh+c9GOQCg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

Connection is Upgrade , Upgrade is websocket , что означает информирование таких серверов, как Nginx и Apache, о том, что это соединение не является HTTP-соединением, а по сути является websocket , поэтому сервер перенаправит его на обработку соответствующей задачи websocket;

Sec-WebSocket-Key — это значение в кодировке Base64, случайно сгенерированное браузером для проверки правильности подключения к серверу;
Sec-WebSocket-Versio представляет версию используемой службы веб-сокетов;

В заголовке ответа:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: py9bt3HbjicUUmFWJfI0nhGombo=

Видно, что возвращаемый код состояния равен 101, что указывает на то, что протокол переключен;
Upgrade и Connection используются для ответа клиенту, что протокол был успешно переключен;
Поле Sec-WebSocket-Accept соответствует ключу Sec-WebSocket-Key и используется для проверки корректности службы;

стадия соединения

После того, как рукопожатие соединения установлено через HTTP, следующим шагом является настоящее соединение Websocket, которое отправляет и получает данные на основе TCP, а Websocket инкапсулирует и открывает интерфейс.

WSS

В протоколе HTTP запросы HTTPS (HTTP + TCL) часто используются для шифрования и обеспечения безопасности;
Соответственно в протоколе Websocket может использоваться и шифрованная передача — wss, напримерwss://localhost:8080.

Используется тот же сертификат HTTPS здесь, как правило, называется обработкой сертификатов NGINX и другие сервисы.