Объясните Websocket простыми словами (1) Протокол Websocket

внешний интерфейс сервер Charles WebSocket

предисловие

В последнее время, в части связи в реальном времени при построении двух систем, подытожу то, что я узнал.

Это серия статей, в основном задуманных в настоящее время в четырех частях.

текст

В этой статье в основном рассказывается о том, что такое Websocket, и о содержании его протокола.

Протокол WebSocket реализует связь между клиентом, выполняющим ненадежный код в контролируемой среде, и удаленным хостом, с которого код согласился на связь.полнодуплексная связь. Протокол включает в себя положение квитирования фазы открытия и определение основных кадров сообщений для связи. Он основан на TCP.Цель этой технологии — предоставить механизм для приложений на основе браузера, которым требуется двусторонняя связь с сервером, не полагаясь на открытие нескольких HTTP-соединений.(例如,使用XMLHttpRequest或<iframe>和长轮询).

Что может вебсокет

В прошлом при создании веб-приложений, требующих двусторонней связи между клиентом и службой (например, приложения для обмена мгновенными сообщениями и игровые приложения), требовалось запрашивать обновления на сервере через HTTP, а затем отправлять другой запрос, если это было push-сообщение (многие приложений сегодня все еще используется таким образом). Есть некоторые проблемы с этим.

  • Сервер вынужден предоставлять два типа интерфейсов: один для клиента для опроса новых сообщений, а другой для клиента для отправки сообщений на сервер.
  • Протокол HTTP имеет больше накладных расходов, каждый раз, когда отправляется сообщение, будет информация заголовка HTTP, и если Keep-Alive не используется, каждый раз требуется рукопожатие.
  • Сценарии на стороне клиента, такие как JS, также могут нуждаться в отслеживании всего процесса, а это означает, что после отправки сообщения мне может потребоваться отследить возврат сообщения.

Простое решение — использовать одно TCP-соединение для двунаправленной передачи. Вот почему предоставляется протокол WebSocket. В сочетании с WebSocket API [WSAPI] он обеспечивает альтернативу HTTP-опросу для двунаправленной связи с веб-страницы на удаленный сервер.

Соглашение

Протокол Websocket в основном состоит из двух частей, одна из которыхПожать рукиправило, другоеСпособ передачи данных и формат носителя. Найдите его онлайн здесьПример (нажмите здесь), вы можете просмотреть содержимое Сети в инструментах разработчика.

После успешного рукопожатия клиента и сервера начинается часть передачи данных, которая представляет собой полнодуплексную связь. Базовая единица передачи данных между клиентом и сервером называется «Сообщения» в соответствии со спецификацией. В реальной сети эти сообщения состоят из одного или нескольких фреймов.Фрейм в сообщении Websocket не соответствует фрейму в компьютерной сети.Структура фрейма будет подробно описана позже.

Пожать руки

Рукопожатие открытой фазы предназначено для совместимости с серверным программным обеспечением и промежуточным ПО на основе HTTP, поэтому один порт может использоваться как для HTTP-клиентов, взаимодействующих с сервером, так и для клиентов WebSocket, взаимодействующих с сервером. Таким образом, рукопожатие для клиента WebSocket представляет собой HTTP-запрос на обновление (код состояния HTTP 101):

Заголовок запроса информации о запросе (вверху) и заголовок ответа (внизу) в Charles

Вот несколько полей и их соображения о полях

Происхождение (заголовок запроса)

Originиспользуется для указания источника запроса,OriginЗаголовок в основном используется для защиты сервера Websocket от несанкционированных вызовов междоменных сценариев к API Websocket. То есть, если вы не хотите устанавливать соединение с сервером для несанкционированного междоменного доступа, сервер может использовать это поле, чтобы определить исходный домен и выборочно отклонить его.

Sec-WebSocket-Key (заголовок запроса) и Sec-WebSocket-Accept (заголовок ответа)

С другой стороны, протокол Websocket должен гарантировать, что запрос на подключение к Websocket, инициированный клиентом, будет распознан только тем сервером, который понимает протокол Websocket.

Really, as you are mentioned, if you are aware of websockets (that is what to be checked), you could pretend to be a websocket server by sending correct response. But then, if you will not act correctly (e.g. form frames correctly), it will be considered as a protocol violation. Actually, you can write a websocket server that is incorrect, but there will be not much use in it.

And another purpose is to prevent clients accidentally requesting websockets upgrade not expecting it (say, by adding corresponding headers manually and then expecting smth else). Sec-WebSocket-Key and other related headers are prohibited to be set using setRequestHeader method in browsers.

ссылка на переполнение стека

передача информации

Ниже описывается структура фрейма.

Как упоминалось ранее, базовая единица передачи данных между клиентом и сервером называется «Сообщения» в соответствии со спецификацией. В реальной сети эти сообщения состоят из одного или нескольких кадров.

  1. FIN, указывающий, является ли кадр последним кадром в сообщении (ранее я говорил, что сообщение может состоять из нескольких кадров)
  2. RSV1-3,долженравно 0, если только расширение не определяет значение ненулевого значения.
  3. Opcode,это важнее, следующие значения определены протоколом
    • %x0 denotes a continuation frame

    • %x1 представляет собой текстовую рамку

    • %x2 представляет собой двоичный кадр

    • %x3-7 are reserved for further non-control frames

    • %x8 Указывает, что соединение закрыто

    • %x9 Указывает пинг (относится к обнаружению сердцебиения, что будет обсуждаться позже)

    • %xA Указывает на понг (относится к обнаружению сердцебиения, что будет обсуждаться позже)

    • %xB-F are reserved for further control frames

  4. Mask, это должно указать, следует ли маскировать «данные полезной нагрузки». это и последнееMasking-keyСвязанный
  5. Payload len, длина данных, не вдаваться в подробности.
  6. Masking-key, я не буду вдаваться в подробности здесь, дайтеЗначение маски в Websocket
  7. Payload data, данные, которые фрейм действительно хочет отправить, могут быть любой длины, но хотя в теории нет ограничения на размер фрейма, отправляемые данные не могут быть слишком большими, иначе это приведет к сбою.Эффективное использование пропускной способности сети, как упоминалось выше, Websocket обеспечивает сегментирование.

Делать математикуНиже приведен фрагмент контента от charles

// 十六进制
81 84 3a a6 ac e4 51 c3 c7 81

// 二进制
10000001 10000100 00111010 10100110 10101101 11100100 01010001 11010011 11010111 10000001

код операции 0001, 0x1 представляет текстовый фрейм

Длина полезной нагрузки равна 0000100, а 0x4 означает, что длина составляет 4 байта.

Маска 00111010 10100110 10101101 11100100

Полезная нагрузка: 01010001 11010011 11010111 10000001

Информацию о конкретной обработке см.Исходный код Node.js wsодин из нихbuffer-utils

Использование и API Websocket

После обсуждения части протокола Websocket давайте поговорим о том, как относиться к веб-API.

// 客户端
var ws = new WebSocket('wss://example.com/socket'); ➊
ws.onerror = function (error) { ... } ➋ 
ws.onclose = function () { ... } ➌
ws.onopen = function () { ➍
ws.send("Connection established. Hello server!"); ➎
}
ws.onmessage = function(msg) { ➏ 
    if(msg.data instanceof Blob) { ➐
        processBlob(msg.data);
    } else {
        processText(msg.data);
    }
}
  1. Откройте новое защищенное соединение WebSocket (wss)
  2. необязательный обратный вызов, вызываемый при сбое соединения
  3. необязательный обратный вызов, вызываемый при разрыве соединения
  4. необязательный обратный вызов, вызываемый при установлении соединения WebSocket
  5. Сначала клиент отправляет сообщение серверу
  6. Функция обратного вызова, которая вызывается каждый раз, когда сервер отправляет сообщение обратно
  7. В зависимости от полученного сообщения решите вызвать двоичную или текстовую логику обработки.

Обнаружение сердцебиения

В процессе использования websocket иногда клиентская сеть закрывается, а на сервере в это время не срабатывает событие onclose. Это будет:

  • резервное соединение
  • Сервер будет продолжать отправлять данные клиенту, и данные будут потеряны

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

Обработка пульса модулем ws

Как модуль ws обнаруживает и закрывает разорванные соединения с помощью обнаружения сердцебиения

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

function noop() {}

function heartbeat() {
  this.isAlive = true;
}

wss.on('connection', function connection(ws) {
  ws.isAlive = true;
  ws.on('pong', heartbeat);
});

const interval = setInterval(function ping() {
  wss.clients.forEach(function each(ws) {
    if (ws.isAlive === false) return ws.terminate();

    ws.isAlive = false;
    ws.ping(noop);
  });
}, 30000);

Согласно спецификации ответное сообщение Pong автоматически отправляется при получении сообщения Ping.

Решите сосуществование ws и wss

Ниже приведена моя конфигурация nginx, кстати, с добавленной балансировкой нагрузки. Тест доступен, но сертификат немного проблематичен, потому что он самоподписанный.

Как Websocket выполняет аутентификацию

Как правило, аутентификация Websocket происходит на этапе рукопожатия и подтверждается содержимым запроса. Типичным примером является включение параметров в URL-адрес.

new WebSocket("ws://localhost:3000?token=xxxxxxxxxxxxxxxxxxxx");

Прямая трансляция Taobao также использует этот метод для аутентификации личности.

Взяв в качестве примера реализацию модуля ws npm, он предоставляет метод verifyClient при создании сервера Websocket.

const wss = new WebSocket.Server({
  host: SystemConfig.WEBSOCKET_server_host,
  port: SystemConfig.WEBSOCKET_server_port,
  // 验证token识别身份
  verifyClient: (info) => {
    const token = url.parse(info.req.url, true).query.token
    let user
    console.log('[verifyClient] start validate')
    // 如果token过期会爆TokenExpiredError
    if (token) {
      try {
        user = jwt.verify(token, publicKey)
        console.log(`[verifyClient] user ${user.name} logined`)
      } catch (e) {
        console.log('[verifyClient] token expired')
        return false
      }
    }
    // verify token and parse user object
    if (user) {
      info.req.user = user
      return true
    } else {
      info.req.user = {
        name: `游客${parseInt(Math.random() * 1000000)}`,
        mail: ''
      }
      return true
    }
  }
})

Соответствующий исходный код ws находится по адресуws/websocket-server

  // ...
  if (this.options.verifyClient) {
    const info = {
      origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
      secure: !!(req.connection.authorized || req.connection.encrypted),
      req
    };

    if (this.options.verifyClient.length === 2) {
      this.options.verifyClient(info, (verified, code, message) => {
        if (!verified) return abortHandshake(socket, code || 401, message);
        this.completeUpgrade(extensions, req, socket, head, cb);
      });
      return;
    }

    if (!this.options.verifyClient(info)) return abortHandshake(socket, 401);
  }
  this.completeUpgrade(extensions, req, socket, head, cb);
}

постскриптум

Использованная литература:

"rfc6455》 Протокол веб-сокета

"Высокопроизводительная браузерная сеть" - [Плюс] Илья Григорик

Изучение протокола WebSocket — принципы реализации сверху вниз (пересмотренное издание)