предисловие
При общении между браузером и сервером традиционный 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 и другие сервисы.