В этой статье Brother Abao начнет с нескольких аспектов и познакомит вас с технологией WebSocket во всех направлениях. Прочитав эту статью, вы будете знать следующее:
- Понимать историю рождения WebSocket, что такое WebSocket и его преимущества;
- Понимать, какие API-интерфейсы содержит WebSocket и как использовать API-интерфейс WebSocket для отправки простого текста и двоичных данных;
- Понимание протокола рукопожатия WebSocket, формата фрейма данных, алгоритма маски и других связанных знаний;
- Узнайте, как реализовать сервер WebSocket, который поддерживает отправку обычного текста.
напоследокАпо есть что сказатьНа этом занятии брат Абао расскажет о взаимосвязи между WebSocket и HTTP, в чем разница между WebSocket и длинным опросом, что такое пульс WebSocket и что такое Socket.
Рекомендуемое чтение (Спасибо за вашу поддержку и поддержку 🌹🌹🌹):
- Веб-работники, которых вы не знаете (Часть 1) [7,8 тыс. слов | Предупреждение о нескольких изображениях](424+ 👍)
- Капли, о которых вы не знали(215+ 👍)
- WeakMap, которого вы не знаете(55+ 👍)
- Воспроизвести внешний видеоплеер | Предупреждение о нескольких изображениях(708+ 👍)
- Играйте с интерфейсным двоичным кодом(359+ 👍)
Теперь давайте перейдем к теме.Чтобы каждый мог лучше понять и освоить технологию WebSocket, давайте сначала представим, что такое WebSocket.
1. Что такое веб-сокет
1.1 История рождения WebSocket
В первые дни многие веб-сайты использовали опросы для реализации технологии push. Опрос означает, что браузер через регулярные промежутки времени отправляет HTTP-запросы на сервер, а затем сервер возвращает последние данные клиенту. Распространенные методы опроса делятся на опрос и длинный опрос, разница между которыми показана на следующем рисунке:
Чтобы более интуитивно почувствовать разницу между polling и long polling, давайте взглянем на конкретный код:
Этот традиционный режим имеет очевидные недостатки, то есть браузер должен постоянно отправлять запросы на сервер, однако HTTP-запросы и ответы могут содержать длинные заголовки, а действительно достоверные данные могут составлять лишь небольшую часть, поэтому это будет потреблять много ресурсов пропускной способности.
Новая технология опросаComet. Хотя этот метод обеспечивает двустороннюю связь, он по-прежнему требует повторных запросов. И постоянное соединение HTTP, обычно используемое в Comet, также потребляет ресурсы сервера.
В этом случае HTML5 определяет протокол WebSocket, который может лучше экономить ресурсы сервера и пропускную способность, а также обеспечивать больше связи в реальном времени. Веб-сокеты используют универсальный идентификатор ресурса (URI) ws или wss, где wss означает веб-сокет с TLS. Такие как:
ws://echo.websocket.org
wss://echo.websocket.org
WebSocket использует тот же TCP-порт, что и HTTP и HTTPS, и может обходить большинство ограничений брандмауэра. По умолчанию протокол WebSocket использует порт 80; при работе через TLS по умолчанию используется порт 443.
1.2 Введение в веб-сокет
WebSocket — это сетевой транспортный протокол, который обеспечивает полнодуплексную связь по одному TCP-соединению и находится на прикладном уровне модели OSI. Протокол WebSocket был стандартизирован IETF в 2011 году какRFC 6455, послеRFC 7936Дополнительные спецификации.
WebSocket упрощает обмен данными между клиентом и сервером, позволяя серверу активно передавать данные клиенту. В API WebSocket браузеру и серверу нужно только выполнить рукопожатие, и между ними может быть создано постоянное соединение, и может быть выполнена двусторонняя передача данных.
После представления связанного содержимого опроса и WebSocket давайте посмотрим на разницу между опросом XHR и WebSocket:
1.3 Преимущества веб-сокета
- Меньше затрат на контроль. Заголовки пакетов, используемые для управления протоколом, относительно малы при обмене данными между сервером и клиентом после создания соединения.
- Более высокая производительность в реальном времени. Поскольку протокол является полнодуплексным, сервер может активно отправлять данные клиенту в любое время. По сравнению с HTTP-запросами, которые должны ждать, пока клиент инициирует запрос, сервер может ответить, и задержка значительно ниже.
- Оставаться на связи. В отличие от HTTP, WebSocket необходимо сначала создать соединение, что делает его протоколом с отслеживанием состояния, а затем некоторая информация о состоянии может быть опущена при обмене данными.
- Лучшая поддержка двоичного кода. WebSocket определяет бинарные фреймы, которые упрощают обработку бинарного контента, чем HTTP.
- Расширения могут поддерживаться. WebSocket определяет расширения, пользователи могут расширять протокол и реализовывать некоторые настраиваемые подпротоколы.
Поскольку WebSocket обладает вышеуказанными преимуществами, он широко используется в сферах мгновенной связи, аудио и видео в реальном времени, онлайн-образования и игр. Для разработчиков интерфейса, если вы хотите использовать мощные возможности, предоставляемые WebSocket, вы должны сначала освоить API WebSocket.Давайте взглянем на API WebSocket.
2. API веб-сокетов
Прежде чем представить WebSocket API, давайте посмотрим на его совместимость:
(Источник изображения: https://caniuse.com/#search=WebSocket)
Как видно из вышеуказанного рисунка, текущие основные веб-браузеры поддерживают Websocket, поэтому мы можем безопасно использовать его в большинстве проектов.
Чтобы использовать возможности, предоставляемые WebSocket в браузере, мы должны сначала создать объект WebSocket, который предоставляет API для создания и управления соединениями WebSocket, а также для отправки и получения данных через соединение.
Используя конструктор WebSocket, мы можем легко создать объект WebSocket. Далее мы представим API WebSocket с четырех аспектов: конструктор WebSocket, свойства, методы объекта WebSocket и события, связанные с WebSocket.Во-первых, мы начнем с конструктора WebSocket:
2.1 Конструктор
Синтаксис конструктора WebSocket:
const myWebSocket = new WebSocket(url [, protocols]);
Соответствующие параметры описываются следующим образом:
- url: указывает URL-адрес соединения, который является URL-адресом, на который ответит сервер WebSocket.
- протоколы (необязательно): строка протокола или массив, содержащий строки протокола. Эти строки используются для указания подпротоколов, чтобы один сервер мог реализовать несколько подпротоколов WebSocket. Например, вы можете захотеть, чтобы сервер мог обрабатывать различные типы взаимодействий в соответствии с указанным протоколом. Если строка протокола не указана, предполагается пустая строка.
Выдает, когда порт, пытающийся подключиться, заблокированSECURITY_ERR
аномальный.
2.2 Свойства
Объекты WebSocket содержат следующие свойства:
Конкретное значение каждого атрибута следующее:
- binaryType: использовать соединение с двоичным типом данных.
- bufferedAmount (только для чтения): количество байтов, не отправленных на сервер.
- extensions (только для чтения): выберите расширения сервера.
- onclose: используется для указания функции обратного вызова после закрытия соединения.
- onerror: используется для указания функции обратного вызова после сбоя соединения.
- onmessage: используется для указания функции обратного вызова при получении информации с сервера.
- onopen: используется для указания функции обратного вызова после успешного подключения.
- протокол (только для чтения): используется для возврата имени подпротокола, выбранного сервером.
- readyState (только для чтения): возвращает состояние подключения текущего WebSocket, есть 4 состояния:
- CONNECTING — подключение, соответствующее значение равно 0;
- OPEN — подключен и может обмениваться данными, соответствующее значение равно 1;
- ЗАКРЫТИЕ — соединение закрывается, соответствующее значение равно 2;
- CLOSED — соединение было закрыто или соединение не было установлено, что соответствует значению 3.
- url (только для чтения): возвращаемое значение — это абсолютный путь URL-адреса, когда конструктор создал объект экземпляра WebSocket.
2.3 Методы
- close([код[ причина]]): этот метод используется для закрытия соединения WebSocket, если соединение уже закрыто, этот метод ничего не делает.
- send(data): этот метод ставит в очередь данные, которые необходимо передать на сервер через соединение WebSocket, и увеличивает значение bufferedAmount в соответствии с размером передаваемых данных. Если данные не могут быть переданы (например, данные необходимо буферизовать, а буфер заполнен), сокет закроется сам.
2.4 События
Используйте addEventListener() или назначьте прослушиватель событий свойству oneventname объекта WebSocket для прослушивания следующих событий.
- close: срабатывает при закрытии соединения WebSocket, также может быть установлено с помощью свойства onclose.
- ошибка: срабатывает, когда соединение WebSocket закрывается из-за ошибки, также может быть установлено с помощью свойства onerror.
- message: срабатывает при получении данных через WebSocket, а также может быть установлено через свойство onmessage.
- open: запускается при успешном соединении WebSocket, а также может быть установлено с помощью свойства onopen.
Познакомьтесь с WebSocket API, приведем пример отправки обычного текста с помощью WebSocket.
2.5 Отправка обычного текста
В приведенном выше примере мы создали две текстовые области на странице, одна для храненияданные для отправкиа такжеданные, возвращаемые сервером. После того, как пользователь ввел текст для отправки, нажмитеОтправитьПри нажатии кнопки вводимый текст будет отправлен на сервер, и после того, как сервер успешно получит сообщение, он вернет полученное сообщение клиенту в неизменном виде.
// const socket = new WebSocket("ws://echo.websocket.org");
// const sendMsgContainer = document.querySelector("#sendMessage");
function send() {
const message = sendMsgContainer.value;
if (socket.readyState !== WebSocket.OPEN) {
console.log("连接未建立,还不能发送消息");
return;
}
if (message) socket.send(message);
}
Конечно, после того, как клиент получит сообщение, возвращенное сервером, он сохранит соответствующее текстовое содержимое вполученные данныеСоответствующее текстовое поле textarea.
// const socket = new WebSocket("ws://echo.websocket.org");
// const receivedMsgContainer = document.querySelector("#receivedMessage");
socket.addEventListener("message", function (event) {
console.log("Message from server ", event.data);
receivedMsgContainer.value = event.data;
});
Чтобы более интуитивно понять описанный выше процесс взаимодействия с данными, давайте воспользуемся инструментами разработчика браузера Chrome, чтобы взглянуть на соответствующий процесс:
Полный код, соответствующий приведенному выше примеру, выглядит следующим образом:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WebSocket 发送普通文本示例</title>
<style>
.block {
flex: 1;
}
</style>
</head>
<body>
<h3>阿宝哥:WebSocket 发送普通文本示例</h3>
<div style="display: flex;">
<div class="block">
<p>即将发送的数据:<button onclick="send()">发送</button></p>
<textarea id="sendMessage" rows="5" cols="15"></textarea>
</div>
<div class="block">
<p>接收的数据:</p>
<textarea id="receivedMessage" rows="5" cols="15"></textarea>
</div>
</div>
<script>
const sendMsgContainer = document.querySelector("#sendMessage");
const receivedMsgContainer = document.querySelector("#receivedMessage");
const socket = new WebSocket("ws://echo.websocket.org");
// 监听连接成功事件
socket.addEventListener("open", function (event) {
console.log("连接成功,可以开始通讯");
});
// 监听消息
socket.addEventListener("message", function (event) {
console.log("Message from server ", event.data);
receivedMsgContainer.value = event.data;
});
function send() {
const message = sendMsgContainer.value;
if (socket.readyState !== WebSocket.OPEN) {
console.log("连接未建立,还不能发送消息");
return;
}
if (message) socket.send(message);
}
</script>
</body>
</html>
Фактически, помимо поддержки отправки обычного текста, WebSocket также поддерживает отправку двоичных данных, таких как объекты ArrayBuffer, объекты Blob или объекты ArrayBufferView:
const socket = new WebSocket("ws://echo.websocket.org");
socket.onopen = function () {
// 发送UTF-8编码的文本信息
socket.send("Hello Echo Server!");
// 发送UTF-8编码的JSON数据
socket.send(JSON.stringify({ msg: "我是阿宝哥" }));
// 发送二进制ArrayBuffer
const buffer = new ArrayBuffer(128);
socket.send(buffer);
// 发送二进制ArrayBufferView
const intview = new Uint32Array(buffer);
socket.send(intview);
// 发送二进制Blob
const blob = new Blob([buffer]);
socket.send(blob);
};
После успешного выполнения приведенного выше кода мы можем увидеть соответствующий процесс взаимодействия с данными через инструменты разработчика Chrome:
Давайте рассмотрим отправку объектов Blob в качестве примера, чтобы понять, как отправлять двоичные данные.
Blob (Binary Large Object) представляет собой большой объект двоичного типа. В системе управления базами данных двоичные данные хранятся как набор одного объекта. Блобы обычно представляют собой изображения, звуковые или мультимедийные файлы.Объекты типа Blob в JavaScript представляют собой неизменяемые файловоподобные примитивные данные.
Для тех, кто интересуется Blob, вы можете прочитать"Клякса, которую вы не знаете"Эта статья.
2.6 Отправка двоичных данных
В приведенном выше примере мы создали две текстовые области на странице, одна для храненияданные для отправкиа такжеданные, возвращаемые сервером. После того, как пользователь ввел текст для отправки, нажмитеОтправитьКогда кнопка нажата, мы сначала получим входной текст, завернем текст в объект Blob и отправим его на сервер.После того, как сервер успешно получит сообщение, он вернет полученное сообщение клиенту без изменений.
Когда браузер получает новое сообщение, если это текстовые данные, он автоматически преобразует их в объект DOMString, если это двоичные данные или объект Blob, он передает их напрямую приложению, а приложение само отвечает в соответствии с ними. к возвращаемому типу данных.
код отправки данных
// const socket = new WebSocket("ws://echo.websocket.org");
// const sendMsgContainer = document.querySelector("#sendMessage");
function send() {
const message = sendMsgContainer.value;
if (socket.readyState !== WebSocket.OPEN) {
console.log("连接未建立,还不能发送消息");
return;
}
const blob = new Blob([message], { type: "text/plain" });
if (message) socket.send(blob);
console.log(`未发送至服务器的字节数:${socket.bufferedAmount}`);
}
Конечно, после того, как клиент получит сообщение, возвращенное сервером, он определит возвращаемый тип данных.Если это тип Blob, он вызовет метод text() объекта Blob для получения содержимого в формате UTF-8. сохраняется в объекте Blob, а затем помещается соответствующее текстовое содержимое сохраняется вполученные данныеСоответствующее текстовое поле textarea.
код приема данных
// const socket = new WebSocket("ws://echo.websocket.org");
// const receivedMsgContainer = document.querySelector("#receivedMessage");
socket.addEventListener("message", async function (event) {
console.log("Message from server ", event.data);
const receivedData = event.data;
if (receivedData instanceof Blob) {
receivedMsgContainer.value = await receivedData.text();
} else {
receivedMsgContainer.value = receivedData;
}
});
Точно так же мы используем инструменты разработчика браузера Chrome и смотрим на соответствующую процедуру:
Из приведенного выше рисунка ясно видно, что при использовании объекта send Blob информация в столбце Data отображаетBinary Message, а для отправки обычного текста информация в поле Данные предназначена для непосредственного отображения отправленного текстового сообщения.
Полный код, соответствующий приведенному выше примеру, выглядит следующим образом:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WebSocket 发送二进制数据示例</title>
<style>
.block {
flex: 1;
}
</style>
</head>
<body>
<h3>阿宝哥:WebSocket 发送二进制数据示例</h3>
<div style="display: flex;">
<div class="block">
<p>待发送的数据:<button onclick="send()">发送</button></p>
<textarea id="sendMessage" rows="5" cols="15"></textarea>
</div>
<div class="block">
<p>接收的数据:</p>
<textarea id="receivedMessage" rows="5" cols="15"></textarea>
</div>
</div>
<script>
const sendMsgContainer = document.querySelector("#sendMessage");
const receivedMsgContainer = document.querySelector("#receivedMessage");
const socket = new WebSocket("ws://echo.websocket.org");
// 监听连接成功事件
socket.addEventListener("open", function (event) {
console.log("连接成功,可以开始通讯");
});
// 监听消息
socket.addEventListener("message", async function (event) {
console.log("Message from server ", event.data);
const receivedData = event.data;
if (receivedData instanceof Blob) {
receivedMsgContainer.value = await receivedData.text();
} else {
receivedMsgContainer.value = receivedData;
}
});
function send() {
const message = sendMsgContainer.value;
if (socket.readyState !== WebSocket.OPEN) {
console.log("连接未建立,还不能发送消息");
return;
}
const blob = new Blob([message], { type: "text/plain" });
if (message) socket.send(blob);
console.log(`未发送至服务器的字节数:${socket.bufferedAmount}`);
}
</script>
</body>
</html>
Могут быть некоторые друзья, которые считают, что этого недостаточно после изучения API WebSocket. Далее брат Бао предложит вам реализовать сервер WebSocket, поддерживающий отправку обычного текста.
3. Рукописный сервер WebSocket
Прежде чем представить, как написать сервер WebSocket, нам нужно понять жизненный цикл соединения WebSocket.
Как видно из приведенного выше рисунка, перед использованием WebSocket для реализации полнодуплексной связи требуется рукопожатие (Handshake) между клиентом и сервером, и после завершения рукопожатия можно начать двустороннюю передачу данных.
Рукопожатие происходит после создания канала связи, но до начала передачи информации.Квитирование используется для достижения таких параметров, как скорость передачи информации, алфавит, четность, процедуры прерывания и другие функции протокола.Квитирование помогает системам или устройствам различной структуры соединяться в канале связи без необходимости вручную задавать параметры.
Поскольку рукопожатие является первой ссылкой в жизненном цикле соединения WebSocket, давайте сначала проанализируем протокол рукопожатия WebSocket.
3.1 Протокол рукопожатия
Протокол WebSocket относится к протоколу прикладного уровня, который зависит от протокола TCP транспортного уровня. WebSocket по протоколу HTTP/1.1101Код состояния для рукопожатия. Чтобы создать подключение Websocket, запрос выполнен через браузер, и сервер отвечает, процесс, обычно называемый «рукопожатием».
Использование HTTP для завершения рукопожатия имеет несколько преимуществ. Во-первых, сделайте WebSocket совместимым с существующей HTTP-инфраструктурой: заставьте серверы WebSocket работать на портах 80 и 443, которые обычно являются единственными портами, открытыми для клиентов. Во-вторых, давайте повторно используем и расширим поток обновления HTTP, чтобы добавить настраиваемые заголовки WebSocket для завершения согласования.
Давайте возьмем пример отправки обычного текста, который был продемонстрирован ранее, в качестве примера для детального анализа процесса рукопожатия.
3.1.1 Запрос клиента
GET ws://echo.websocket.org/ HTTP/1.1
Host: echo.websocket.org
Origin: file://
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: Zx8rNEkBE4xnwifpuh8DHQ==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Примечание. Некоторые заголовки HTTP-запросов были проигнорированы.
Поле Описание
- В Connection должен быть установлен Upgrade, чтобы указать, что клиент хочет, чтобы соединение было обновлено.
- В поле Upgrade должно быть указано значение websocket, что указывает на то, что вы хотите перейти на протокол WebSocket.
- Sec-WebSocket-Version указывает поддерживаемую версию WebSocket. Требуемая версия RFC6455 — 13, и все предыдущие черновые версии должны быть объявлены устаревшими.
- Sec-WebSocket-Key — это случайная строка, и сервер будет использовать эти данные для создания дайджеста сообщения SHA-1. Добавьте «Sec-WebSocket-Key» в специальную строку"258EAFA5-E914-47DA-95CA-C5AB0DC85B11", затем вычислите дайджест SHA-1, затем выполните кодирование Base64 и верните результат клиенту в виде значения заголовка «Sec-WebSocket-Accept». Делая это, вы можете попытаться предотвратить ошибочное принятие обычных HTTP-запросов за протокол WebSocket.
- Sec-WebSocket-Extensions используется для согласования расширения WebSocket, которое будет использоваться в этом соединении: клиент отправляет поддерживаемое расширение, а сервер подтверждает, что он поддерживает одно или несколько расширений, возвращая тот же заголовок.
- Поле Origin является необязательным и обычно используется для указания страницы, на которой это соединение WebSocket инициируется в браузере, аналогично Referer. Однако, в отличие от Referer, Origin содержит только протокол и имя хоста.
3.1.2 Ответ сервера
HTTP/1.1 101 Web Socket Protocol Handshake ①
Connection: Upgrade ②
Upgrade: websocket ③
Sec-WebSocket-Accept: 52Rg3vW4JQ1yWpkvFlsTsiezlqw= ④
Примечание. Некоторые заголовки ответов HTTP были проигнорированы.
- ① Код ответа 101 подтверждает переход на протокол WebSocket.
- ② Установите значение заголовка Connection на «Upgrade», чтобы указать, что это запрос на обновление. Протокол HTTP предоставляет специальный механизм, который позволяет обновить установленное соединение до нового, несовместимого протокола.
- ③ Заголовок Upgrade указывает одно или несколько имен протоколов, отсортированных по приоритету и разделенных запятыми. Это означает переход на протокол WebSocket.
- ④ Поддержка подписанного протокола проверки ключ-значение.
После введения протокола рукопожатия WebSocket компания Brother Abao будет использовать Node.js для разработки нашего сервера WebSocket.
3.2 Реализация функции рукопожатия
Чтобы разработать сервер WebSocket, сначала нам нужно реализовать функцию рукопожатия, здесь Brother Abao использует встроенный Node.jshttpмодуль для создания HTTP-сервера, конкретный код выглядит следующим образом:
const http = require("http");
const port = 8888;
const { generateAcceptValue } = require("./util");
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
res.end("大家好,我是阿宝哥。感谢你阅读“你不知道的WebSocket”");
});
server.on("upgrade", function (req, socket) {
if (req.headers["upgrade"] !== "websocket") {
socket.end("HTTP/1.1 400 Bad Request");
return;
}
// 读取客户端提供的Sec-WebSocket-Key
const secWsKey = req.headers["sec-websocket-key"];
// 使用SHA-1算法生成Sec-WebSocket-Accept
const hash = generateAcceptValue(secWsKey);
// 设置HTTP响应头
const responseHeaders = [
"HTTP/1.1 101 Web Socket Protocol Handshake",
"Upgrade: WebSocket",
"Connection: Upgrade",
`Sec-WebSocket-Accept: ${hash}`,
];
// 返回握手请求的响应信息
socket.write(responseHeaders.join("\r\n") + "\r\n\r\n");
});
server.listen(port, () =>
console.log(`Server running at http://localhost:${port}`)
);
В приведенном выше коде мы впервые представилиhttpмодуль, а затем, вызвав модульcreateServer()
метод создания HTTP-сервера, затем мы слушаемupgrade
Событие, которое запускается каждый раз, когда сервер отвечает на запрос на обновление. Поскольку наш сервер поддерживает только обновление до протокола WebSocket, мы вернем «400 Bad Request», если клиент запрашивает обновление до протокола, отличного от WebSocket.
Когда сервер получает запрос рукопожатия, обновленный до WebSocket, он сначала получит его из заголовка запроса."SEC-WebSocket-Ключ"значение, затем добавьте к этому значению специальную строку"258EAFA5-E914-47DA-95CA-C5AB0DC85B11", затем вычислите дайджест SHA-1, затем Base64 кодирует результат как"Sec-WebSocket-принять"Значение заголовка, возвращаемое клиенту.
Вышеуказанный процесс кажется немного громоздким, на самом деле, используя встроенный Node.jscryptoМодуль, можно сделать несколько строк кода:
// util.js
const crypto = require("crypto");
const MAGIC_KEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
function generateAcceptValue(secWsKey) {
return crypto
.createHash("sha1")
.update(secWsKey + MAGIC_KEY, "utf8")
.digest("base64");
}
После разработки функции рукопожатия мы можем использовать предыдущий пример для тестирования функции. После того, как сервер запущен, нам нужно только сделать простую настройку примера «отправить обычный текст», то есть заменить предыдущий URL-адрес наws://localhost:8888
, вы можете выполнить функциональную проверку.
Заинтересованные ребята могут попробовать.Вот результат локального запуска Abao Ge:
Как видно из рисунка выше, реализованная нами функция рукопожатия уже может нормально работать. Так возможно ли, что рукопожатие не удается? Ответ положительный. такие как сетевые проблемы, исключения сервера илиSec-WebSocket-Acceptзначение неверно.
Далее, Apoge, модифицируйте его"Sec-WebSocket-принять"Создание правил, таких как изменениеMAGIC_KEYзначение, а затем повторно аутентифицировать функцию рукопожатия. В этот момент консоль браузера выведет следующую информацию об исключении:
WebSocket connection to 'ws://localhost:8888/' failed: Error during WebSocket handshake: Incorrect 'Sec-WebSocket-Accept' header value
Если вашему серверу WebSocket необходимо поддерживать подпротоколы, вы можете обратиться к следующему коду для обработки подпротоколов Brother Abao больше не будет их вводить.
// 从请求头中读取子协议
const protocol = req.headers["sec-websocket-protocol"];
// 如果包含子协议,则解析子协议
const protocols = !protocol ? [] : protocol.split(",").map((s) => s.trim());
// 简单起见,我们仅判断是否含有JSON子协议
if (protocols.includes("json")) {
responseHeaders.push(`Sec-WebSocket-Protocol: json`);
}
Хорошо, контент, связанный с протоколом рукопожатия WebSocket, в основном был представлен. Далее мы рассмотрим некоторые основы, которые необходимо знать для разработки возможностей обмена сообщениями.
3.3 Основы обмена сообщениями
В протоколе WebSocket передача данных осуществляется через серию кадров данных. Чтобы избежать добавления масок всех фреймов из-за сетевых посредников (например, некоторых прокси-серверов) или из-за проблем с безопасностью, клиент должен отправить их на сервер. После того, как сервер получит фрейм данных, не добавленных масок, необходимо немедленно закрыть соединение.
3.3.1 Формат кадра данных
Чтобы реализовать обмен сообщениями, мы должны понимать формат фрейма данных WebSocket:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
Могут быть некоторые друзья, которые начинают вести себя немного «глупо», увидев вышеприведенный контент. Давайте подробнее рассмотрим фактический фрейм данных:
На приведенном выше рисунке брат Абао кратко проанализировал формат фрейма данных, соответствующий примеру «отправить обычный текст». Здесь мы дополнительно введем длину полезной нагрузки, потому что эта точка знаний потребуется при разработке функции анализа данных позже.
Длина полезной нагрузки указывает длину «данных полезной нагрузки» в байтах. Имеет следующие ситуации:
- Если значение равно 0-125, оно указывает длину данных полезной нагрузки.
- Если это 126, то следующие 2 байта интерпретируются как 16-разрядное целое число без знака как длина данных полезной нагрузки.
- Если это 127, то следующие 8 байтов интерпретируются как 64-битное целое число без знака (наиболее значимым битом должно быть 0) как длина данных полезной нагрузки.
Величины многобайтовой длины выражаются в сетевом порядке байтов, а длина полезной нагрузки относится к длине «данных расширения» + «данных приложения». Длина «расширенных данных» может быть равна 0, тогда длина полезной нагрузки равна длине «данных приложения».
Кроме того, длина «расширенных данных» составляет 0 байт, если только не были согласованы расширения. В протоколе рукопожатия любое расширение должно указывать длину «данных расширения», как эта длина рассчитывается и как это расширение используется. Если есть расширение, эти «данные расширения» включаются в общую длину полезной нагрузки.
3.3.2 Алгоритм маскирования
Поле маски представляет собой 32-битное значение, выбранное клиентом случайным образом. Значение маски должно быть непредсказуемым. Следовательно, маска должна исходить из сильного источника энтропии, и данная маска не должна позволять серверу или прокси-серверу легко предсказывать последующие кадры. Непредсказуемость масок имеет решающее значение для предотвращения раскрытия авторами вредоносных приложений соответствующих байтов данных в Интернете.
Маскирование не влияет на длину полезной нагрузки данных, и шаги, связанные с маскированием и демаскированием данных, одинаковы. Операции маскирования и демаскирования используют следующие алгоритмы:
j = i MOD 4
transformed-octet-i = original-octet-i XOR masking-key-octet-j
- Оригинальный октет - I: Является ли I-й байт исходных данных.
- преобразованный октет-i: i-й байт преобразованных данных.
- masking-key-octet-j: это j-й байт ключа маски.
Чтобы друзья лучше понимали процесс расчета приведенной выше маски, давайте сравним пример"Я Бобо"Данные замаскированы. здесь"Я Бобо"Соответствующая кодировка UTF-8 выглядит следующим образом:
E6 88 91 E6 98 AF E9 98 BF E5 AE 9D E5 93 A5
И соответствующий Masking-Key0x08f6efb1
, согласно приведенному выше алгоритму, мы можем сделать операцию по маске так:
let uint8 = new Uint8Array([0xE6, 0x88, 0x91, 0xE6, 0x98, 0xAF, 0xE9, 0x98,
0xBF, 0xE5, 0xAE, 0x9D, 0xE5, 0x93, 0xA5]);
let maskingKey = new Uint8Array([0x08, 0xf6, 0xef, 0xb1]);
let maskedUint8 = new Uint8Array(uint8.length);
for (let i = 0, j = 0; i < uint8.length; i++, j = i % 4) {
maskedUint8[i] = uint8[i] ^ maskingKey[j];
}
console.log(Array.from(maskedUint8).map(num=>Number(num).toString(16)).join(' '));
После успешного выполнения приведенного выше кода консоль выведет следующие результаты:
ee 7e 7e 57 90 59 6 29 b7 13 41 2c ed 65 4a
Приведенные выше результаты согласуются со значением, соответствующим полезной нагрузке Masked в WireShark, как показано на следующем рисунке:
В протоколе WebSocket роль маски данных заключается в повышении безопасности протокола. Но маска данных не для защиты самих данных, потому что сам алгоритм является общедоступным и операция не сложная. Так зачем вводить маску данных? Маски данных были введены для предотвращения таких проблем, как атаки с загрязнением кеша прокси, которые существовали в более ранних версиях протокола.
Поняв роль алгоритма маскирования WebSocket и маскирования данных, давайте представим концепцию сегментирования данных.
3.3.3 Разделение данных
Каждое сообщение WebSocket может быть разделено на несколько фреймов данных. Когда получатель WebSocket получает кадр данных, он будет судить, получил ли он последний кадр данных сообщения в соответствии со значением FIN.
Используя FIN и код операции, мы можем отправлять сообщения между фреймами. Код операции сообщает фрейму, что делать. Если это 0x1, полезной нагрузкой является текст. Если это 0x2, полезная нагрузка представляет собой двоичные данные. Однако, если это 0x0, кадр является кадром продолжения. Это означает, что сервер должен объединить полезную нагрузку кадра с последним кадром, полученным от этого клиента.
Для того, чтобы каждый мог лучше понять вышеизложенное, давайте взглянем на источник изMDNПример выше:
Client: FIN=1, opcode=0x1, msg="hello"
Server: (process complete message immediately) Hi.
Client: FIN=0, opcode=0x1, msg="and a"
Server: (listening, new message containing text started)
Client: FIN=0, opcode=0x0, msg="happy new"
Server: (listening, payload concatenated to previous message)
Client: FIN=1, opcode=0x0, msg="year!"
Server: (process complete message) Happy new year to you too!
В приведенном выше примере клиент отправляет серверу два сообщения. Первое сообщение отправляется в одном кадре, а второе сообщение отправляется в трех кадрах.
Первое из них — это полное сообщение (FIN=1 и код операции != 0x0), поэтому сервер может обрабатывать или отвечать по мере необходимости. Второе сообщение представляет собой текстовое сообщение (код операции = 0x1) и FIN = 0, что указывает на то, что сообщение еще не было отправлено и имеются последующие кадры данных. Все остальные части сообщения отправляются с кадрами продолжения (код операции = 0x0), а последний кадр сообщения помечается FIN = 1.
Хорошо, краткое введение в соответствующее содержание шардинга данных. Далее приступим к реализации функции передачи сообщений.
3.4 Реализация обмена сообщениями
Brother Abao разбивает реализацию функции передачи сообщений на две подфункции разбора сообщения и ответа на сообщение.Давайте представим, как реализовать эти две подфункции соответственно.
3.4.1 Анализ сообщений
Используя соответствующие знания, представленные в основах обмена сообщениями, брат Абао реализовал функцию parseMessage для анализа фрейма данных WebSocket, отправленного клиентом. Для простоты здесь обрабатываются только текстовые фреймы, а конкретный код выглядит следующим образом:
function parseMessage(buffer) {
// 第一个字节,包含了FIN位,opcode, 掩码位
const firstByte = buffer.readUInt8(0);
// [FIN, RSV, RSV, RSV, OPCODE, OPCODE, OPCODE, OPCODE];
// 右移7位取首位,1位,表示是否是最后一帧数据
const isFinalFrame = Boolean((firstByte >>> 7) & 0x01);
console.log("isFIN: ", isFinalFrame);
// 取出操作码,低四位
/**
* %x0:表示一个延续帧。当 Opcode 为 0 时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片;
* %x1:表示这是一个文本帧(text frame);
* %x2:表示这是一个二进制帧(binary frame);
* %x3-7:保留的操作代码,用于后续定义的非控制帧;
* %x8:表示连接断开;
* %x9:表示这是一个心跳请求(ping);
* %xA:表示这是一个心跳响应(pong);
* %xB-F:保留的操作代码,用于后续定义的控制帧。
*/
const opcode = firstByte & 0x0f;
if (opcode === 0x08) {
// 连接关闭
return;
}
if (opcode === 0x02) {
// 二进制帧
return;
}
if (opcode === 0x01) {
// 目前只处理文本帧
let offset = 1;
const secondByte = buffer.readUInt8(offset);
// MASK: 1位,表示是否使用了掩码,在发送给服务端的数据帧里必须使用掩码,而服务端返回时不需要掩码
const useMask = Boolean((secondByte >>> 7) & 0x01);
console.log("use MASK: ", useMask);
const payloadLen = secondByte & 0x7f; // 低7位表示载荷字节长度
offset += 1;
// 四个字节的掩码
let MASK = [];
// 如果这个值在0-125之间,则后面的4个字节(32位)就应该被直接识别成掩码;
if (payloadLen <= 0x7d) {
// 载荷长度小于125
MASK = buffer.slice(offset, 4 + offset);
offset += 4;
console.log("payload length: ", payloadLen);
} else if (payloadLen === 0x7e) {
// 如果这个值是126,则后面两个字节(16位)内容应该,被识别成一个16位的二进制数表示数据内容大小;
console.log("payload length: ", buffer.readInt16BE(offset));
// 长度是126, 则后面两个字节作为payload length,32位的掩码
MASK = buffer.slice(offset + 2, offset + 2 + 4);
offset += 6;
} else {
// 如果这个值是127,则后面的8个字节(64位)内容应该被识别成一个64位的二进制数表示数据内容大小
MASK = buffer.slice(offset + 8, offset + 8 + 4);
offset += 12;
}
// 开始读取后面的payload,与掩码计算,得到原来的字节内容
const newBuffer = [];
const dataBuffer = buffer.slice(offset);
for (let i = 0, j = 0; i < dataBuffer.length; i++, j = i % 4) {
const nextBuf = dataBuffer[i];
newBuffer.push(nextBuf ^ MASK[j]);
}
return Buffer.from(newBuffer).toString();
}
return "";
}
После создания функции parseMessage давайте обновим созданный ранее сервер WebSocket:
server.on("upgrade", function (req, socket) {
socket.on("data", (buffer) => {
const message = parseMessage(buffer);
if (message) {
console.log("Message from client:" + message);
} else if (message === null) {
console.log("WebSocket connection closed by the client.");
}
});
if (req.headers["upgrade"] !== "websocket") {
socket.end("HTTP/1.1 400 Bad Request");
return;
}
// 省略已有代码
});
После завершения обновления мы перезапускаем сервер и приступаем к тестированию функциональности парсинга сообщений на примере «отправить обычный текст». Ниже приведена информация, выводимая сервером WebSocket после отправки текстового сообщения «Я Баогэ».
Server running at http://localhost:8888
isFIN: true
use MASK: true
payload length: 15
Message from client:我是阿宝哥
Наблюдая за приведенной выше выходной информацией, наш сервер WebSocket смог успешно проанализировать фрейм данных, содержащий обычный текст, отправленный клиентом, и следующим шагом является реализация функции ответа на сообщение.
3.4.2 Ответ на сообщение
Чтобы вернуть данные клиенту, наш сервер WebSocket также должен инкапсулировать данные в формате кадра данных WebSocket. Как и функция parseMessage, представленная ранее, Brother Abao также инкапсулирует функциюstructReply для инкапсуляции возвращаемых данных.Конкретный код функции выглядит следующим образом:
function constructReply(data) {
const json = JSON.stringify(data);
const jsonByteLength = Buffer.byteLength(json);
// 目前只支持小于65535字节的负载
const lengthByteCount = jsonByteLength < 126 ? 0 : 2;
const payloadLength = lengthByteCount === 0 ? jsonByteLength : 126;
const buffer = Buffer.alloc(2 + lengthByteCount + jsonByteLength);
// 设置数据帧首字节,设置opcode为1,表示文本帧
buffer.writeUInt8(0b10000001, 0);
buffer.writeUInt8(payloadLength, 1);
// 如果payloadLength为126,则后面两个字节(16位)内容应该,被识别成一个16位的二进制数表示数据内容大小
let payloadOffset = 2;
if (lengthByteCount > 0) {
buffer.writeUInt16BE(jsonByteLength, 2);
payloadOffset += lengthByteCount;
}
// 把JSON数据写入到Buffer缓冲区中
buffer.write(json, payloadOffset);
return buffer;
}
После создания функцииstructReply давайте обновим созданный нами ранее сервер WebSocket:
server.on("upgrade", function (req, socket) {
socket.on("data", (buffer) => {
const message = parseMessage(buffer);
if (message) {
console.log("Message from client:" + message);
// 新增以下👇代码
socket.write(constructReply({ message }));
} else if (message === null) {
console.log("WebSocket connection closed by the client.");
}
});
});
На данный момент наш сервер WebSocket разработан, и теперь давайте полностью проверим его работу.
Как видно из рисунка, разработанный нами простой вариант сервера WebSocket уже может нормально обрабатывать обычные текстовые сообщения. Наконец, давайте посмотрим на полный код:
custom-websocket-server.js
const http = require("http");
const port = 8888;
const { generateAcceptValue, parseMessage, constructReply } = require("./util");
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
res.end("大家好,我是阿宝哥。感谢你阅读“你不知道的WebSocket”");
});
server.on("upgrade", function (req, socket) {
socket.on("data", (buffer) => {
const message = parseMessage(buffer);
if (message) {
console.log("Message from client:" + message);
socket.write(constructReply({ message }));
} else if (message === null) {
console.log("WebSocket connection closed by the client.");
}
});
if (req.headers["upgrade"] !== "websocket") {
socket.end("HTTP/1.1 400 Bad Request");
return;
}
// 读取客户端提供的Sec-WebSocket-Key
const secWsKey = req.headers["sec-websocket-key"];
// 使用SHA-1算法生成Sec-WebSocket-Accept
const hash = generateAcceptValue(secWsKey);
// 设置HTTP响应头
const responseHeaders = [
"HTTP/1.1 101 Web Socket Protocol Handshake",
"Upgrade: WebSocket",
"Connection: Upgrade",
`Sec-WebSocket-Accept: ${hash}`,
];
// 返回握手请求的响应信息
socket.write(responseHeaders.join("\r\n") + "\r\n\r\n");
});
server.listen(port, () =>
console.log(`Server running at http://localhost:${port}`)
);
util.js
const crypto = require("crypto");
const MAGIC_KEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
function generateAcceptValue(secWsKey) {
return crypto
.createHash("sha1")
.update(secWsKey + MAGIC_KEY, "utf8")
.digest("base64");
}
function parseMessage(buffer) {
// 第一个字节,包含了FIN位,opcode, 掩码位
const firstByte = buffer.readUInt8(0);
// [FIN, RSV, RSV, RSV, OPCODE, OPCODE, OPCODE, OPCODE];
// 右移7位取首位,1位,表示是否是最后一帧数据
const isFinalFrame = Boolean((firstByte >>> 7) & 0x01);
console.log("isFIN: ", isFinalFrame);
// 取出操作码,低四位
/**
* %x0:表示一个延续帧。当 Opcode 为 0 时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片;
* %x1:表示这是一个文本帧(text frame);
* %x2:表示这是一个二进制帧(binary frame);
* %x3-7:保留的操作代码,用于后续定义的非控制帧;
* %x8:表示连接断开;
* %x9:表示这是一个心跳请求(ping);
* %xA:表示这是一个心跳响应(pong);
* %xB-F:保留的操作代码,用于后续定义的控制帧。
*/
const opcode = firstByte & 0x0f;
if (opcode === 0x08) {
// 连接关闭
return;
}
if (opcode === 0x02) {
// 二进制帧
return;
}
if (opcode === 0x01) {
// 目前只处理文本帧
let offset = 1;
const secondByte = buffer.readUInt8(offset);
// MASK: 1位,表示是否使用了掩码,在发送给服务端的数据帧里必须使用掩码,而服务端返回时不需要掩码
const useMask = Boolean((secondByte >>> 7) & 0x01);
console.log("use MASK: ", useMask);
const payloadLen = secondByte & 0x7f; // 低7位表示载荷字节长度
offset += 1;
// 四个字节的掩码
let MASK = [];
// 如果这个值在0-125之间,则后面的4个字节(32位)就应该被直接识别成掩码;
if (payloadLen <= 0x7d) {
// 载荷长度小于125
MASK = buffer.slice(offset, 4 + offset);
offset += 4;
console.log("payload length: ", payloadLen);
} else if (payloadLen === 0x7e) {
// 如果这个值是126,则后面两个字节(16位)内容应该,被识别成一个16位的二进制数表示数据内容大小;
console.log("payload length: ", buffer.readInt16BE(offset));
// 长度是126, 则后面两个字节作为payload length,32位的掩码
MASK = buffer.slice(offset + 2, offset + 2 + 4);
offset += 6;
} else {
// 如果这个值是127,则后面的8个字节(64位)内容应该被识别成一个64位的二进制数表示数据内容大小
MASK = buffer.slice(offset + 8, offset + 8 + 4);
offset += 12;
}
// 开始读取后面的payload,与掩码计算,得到原来的字节内容
const newBuffer = [];
const dataBuffer = buffer.slice(offset);
for (let i = 0, j = 0; i < dataBuffer.length; i++, j = i % 4) {
const nextBuf = dataBuffer[i];
newBuffer.push(nextBuf ^ MASK[j]);
}
return Buffer.from(newBuffer).toString();
}
return "";
}
function constructReply(data) {
const json = JSON.stringify(data);
const jsonByteLength = Buffer.byteLength(json);
// 目前只支持小于65535字节的负载
const lengthByteCount = jsonByteLength < 126 ? 0 : 2;
const payloadLength = lengthByteCount === 0 ? jsonByteLength : 126;
const buffer = Buffer.alloc(2 + lengthByteCount + jsonByteLength);
// 设置数据帧首字节,设置opcode为1,表示文本帧
buffer.writeUInt8(0b10000001, 0);
buffer.writeUInt8(payloadLength, 1);
// 如果payloadLength为126,则后面两个字节(16位)内容应该,被识别成一个16位的二进制数表示数据内容大小
let payloadOffset = 2;
if (lengthByteCount > 0) {
buffer.writeUInt16BE(jsonByteLength, 2);
payloadOffset += lengthByteCount;
}
// 把JSON数据写入到Buffer缓冲区中
buffer.write(json, payloadOffset);
return buffer;
}
module.exports = {
generateAcceptValue,
parseMessage,
constructReply,
};
По сути, сервер проталкивает информацию в браузер, помимо использования технологии WebSocket можно также использовать SSE (Server-Sent Events). Это позволяет серверу передавать текстовые сообщения клиентам, например, сообщения в реальном времени, созданные на сервере. Для достижения этой цели SSE разработала два компонента:в браузереEventSource APIи новый формат данных «поток событий» (текст/поток событий). Среди них EventSource позволяет клиенту получать push-уведомления сервера в виде событий DOM, а новый формат данных используется для доставки каждого обновления данных.
По сути, SSE обеспечивает эффективную кросс-браузерную реализацию потоковой передачи XHR с доставкой сообщений с использованием только одного длинного HTTP-соединения. Однако вместо того, чтобы самим реализовывать поток XHR, браузер управляет подключением и анализирует сообщение за нас, позволяя нам сосредоточиться только на бизнес-логике. Место ограничено.Для получения более подробной информации о SSE брат Абао не будет представлять его.Друзья, которые заинтересованы в SSE, могут проверить соответствующую информацию самостоятельно.
4. Брату А Бао есть что сказать
4.1 Какое отношение WebSocket имеет к HTTP
WebSocket — это другой протокол и HTTP. Оба находятся на прикладном уровне модели OSI, а протокол TCP зависит от транспортного уровня. Хотя они разные, но в RFC 6455 оговорено: WebSocket предназначен для работы на портах HTTP 80 и 443 и поддерживает HTTP-прокси и посредников, поэтому он совместим с протоколом HTTP. Чтобы добиться совместимости, рукопожатие WebSocket с использованием заголовка HTTP Upgrade измените протокол с HTTP на протокол WebSocket.
Поскольку было упомянутоМодель OSI (модель взаимодействия открытых систем), вот Brother A Bao, чтобы поделиться очень яркой и яркой диаграммой модели OSI:
(Источник изображения: https://www.networkingsphere.com/2019/07/what-is-osi-model.html)
4.2 В чем разница между WebSocket и длинным опросом
Длительный опрос означает, что клиент инициирует запрос.После того, как сервер получает запрос от клиента, сервер не отвечает напрямую, а сначала приостанавливает запрос, а затем определяет, были ли обновлены запрошенные данные. Если есть обновление, он ответит, если данных нет, то будет ждать определенный период времени, прежде чем вернуться.
Суть long polling по-прежнему основана на протоколе HTTP, и это по-прежнему вопросно-ответный (запрос-ответ) режим. После успешного рукопожатия WebSocket представляет собой полнодуплексный TCP-канал, и данные могут активно отправляться с сервера на клиент.
4.3 Что такое пульс WebSocket
Как прием, так и отправка данных в сеть реализованы с помощью SOCKET. Но если этот сокет был отключен, должны быть проблемы при отправке и получении данных. Но как определить, можно ли еще использовать розетку? Это требует создания в системе механизма сердцебиения. Так называемый «пульс» заключается в регулярной отправке пользовательской структуры (пакета пульса или кадра пульса), чтобы другая сторона знала, что она «в сети». для обеспечения действительности ссылки.
Так называемый пакет сердцебиения заключается в том, что клиент регулярно отправляет простую информацию на сервер, чтобы сообщить ему, что я все еще там. Код заключается в отправке фиксированного сообщения на сервер каждые несколько минут, и сервер отвечает фиксированным сообщением после его получения.Если сервер не получает клиентское сообщение в течение нескольких минут, клиент считается отключенным.
Определено в протоколе WebSocketПинг сердцебиенияа такжесердцебиение понгРамка управления:
- Код операции, содержащийся в кадре пинга пульса, равен 0x9. Если получен кадр Heartbeat Ping, конечная точка ДОЛЖНА отправить кадр Heartbeat Pong в ответ, если только не был получен кадр Close. В противном случае терминал ДОЛЖЕН ответить кадром Pong как можно скорее.
- Код операции, содержащийся в кадре Heartbeat Pong, равен 0xA. Кадр Pong, отправленный в ответ, ДОЛЖЕН полностью содержать поле «данные приложения», переданное в кадре Ping. Если терминал принимает кадр Ping, но не отправляет кадр Ping в ответ на предыдущий кадр Ping, терминал может выбрать отправку кадра Pong только для самого последнего обработанного кадра Ping. Кроме того, кадр Pong может быть отправлен автоматически, что действует как одностороннее сердцебиение.
4.4 Что такое сокет
Две программы в сети обмениваются данными через двустороннее коммуникационное соединение. Один конец этого соединения называется сокетом. Поэтому для установления сетевого коммуникационного соединения требуется как минимум пара номеров портов.Socket — это, по сути, инкапсуляция стека протоколов TCP/IP, которая предоставляет интерфейс для программирования TCP или UDP, а не другой протокол..通过 socket,你可以使用 TCP/IP 协议。
Первоначальное значение английского Socket — «отверстие» или «гнездо». Начиная с BSD UNIXсвязь процессаМеханизм, возьми последнее значение. Также обычно называют «разъем", используемый для описания IP-адресов и портов, представляет собой дескриптор цепочки связи, которую можно использовать для реализации связи между разными виртуальными машинами или разными компьютерами.
в интернетехозяин一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。 Socket 正如其英文原义那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供 220 伏交流电, 有的提供 110 伏交流电,有的则提供有线电视节目。客户软件将插头插到不同编号的插座,就可以得到不同的服务。 ——Энциклопедия Байду
Что касается Socket, можно резюмировать следующие моменты:
- Он может реализовать низкоуровневую связь, и почти все прикладные уровни взаимодействуют через сокеты.
- Он инкапсулирует протокол TCP/IP, удобный для вызовов протоколов прикладного уровня, и относится к промежуточному уровню абстракции между ними.
- В наборе протоколов TCP/IP на транспортном уровне есть два общих протокола: TCP и UDP, Эти два протокола отличаются друг от друга, поскольку процесс реализации сокетов с разными параметрами также отличается.
Следующая диаграмма иллюстрирует отношения клиент/сервер API сокетов протокола, ориентированного на соединение.
V. Справочные ресурсы
- Википедия — веб-сокет
- MDN - WebSocket
- MDN - Protocol_upgrade_mechanism
- MDN — Написание сервера WebSocket
- rfc6455
- Полное руководство по веб-производительности
В этой статье используетсяmdniceнабор текста