Быстро понять | Подробное объяснение и практика протокола WebSocket

Java
Быстро понять | Подробное объяснение и практика протокола WebSocket

Обзор

Для протокола WebSocket остается только长连接начальство? Хотите узнать, как реализован протокол WebSokcet? Какую работу тайно выполняет браузер за нашей спиной?

так какПрактика — единственный критерий проверки соглашений, поэтому в этой статье будет изучено устройство протокола WebSocket с одной передачей данных между клиентом и сервером.

Эта статья основана на документации протокола WebSocket.RFC6455Подытожил (уже перевел большой пареньКитайская версия)

Совет, если вы недостаточно знаете о протоколе TCP, рекомендуется прочитатьБуклет протокола TCP

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

Настройка тестовой среды

  • Ошибка ОС 10
  • программное обеспечениеWireSharkдля захвата пакетов
  • Хорошо написанный сервер WebSocket и клиент для локального тестирования

Конфигурация WireShark

Хорошо известно, что WireShark может использоваться для перехвата сетевых пакетов и широко используется в области безопасности и тестирования, поэтому мы можем использовать WireShark для анализа того, что тайно говорят сервер и клиент при использовании протокола WebSocket.

Первым шагом является выбор сетевой карты для мониторинга

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

Второй шаг — отфильтровать ненужные данные.

Как показано на рисунке ниже, установитеtcp.port == 8080Указывает, что мы отслеживаем данные только на порту 8080, что будет объяснено ниже.

Подробное объяснение протокола WebSocket

Протокол рукопожатия — открытие рукопожатия

Если клиент хочет установить соединение WebSocket с сервером, он должен пройти следующий процесс.

0x01 Клиент отправляет запрос на рукопожатие

После того, как клиент устанавливает TCP-соединение с сервером, клиент отправляет запрос подтверждения на сервер в виде сообщения HTTP.Сообщение HTTP должно соответствовать следующим требованиям.

  • HTTP-сообщение должно быть допустимым, а метод запросаGET

  • HTTP-сообщение должно содержать следующие заголовки, чтобы указать, что это запрос установления связи WebSocket.

Upgrade: websocket
Connection: Upgrade
  • Заголовок HTTP-сообщения должен содержатьSec-WebSocket-KeyПоле, это поле в основном используется для проверки протокола WebSocket для предотвращения злоупотреблений, это поле может появиться только один раз, и алгоритм его значения будет подробно объяснен позже.

  • Если этот запрос поступает из браузера, заголовок HTTP-сообщения должен содержатьOriginполе, другие запросы также могут содержать это поле.

  • Заголовок HTTP-сообщения должен содержатьSec-WebSocket-Version, чтобы указать версию WebSocket, а его значение должно быть13

  • Заголовки сообщений HTTP могут содержатьSec-WebSocket-Protocol, чтобы указать подпротокол, который клиент хочет выполнить

  • Заголовки сообщений HTTP могут содержатьSec-WebSocket-Extensions, чтобы указать расширение, которое клиент хочет выполнить (например, плагин сжатия сообщений)

  • HTTP-пакеты могут содержать другие заголовки.

После того, как клиент отправляет HTTP-запрос рукопожатия, клиент не может отправлять какие-либо данные на сервер, пока сервер не вернет ответ.

0x02 ответ сервера

Если сервер решит получить от клиента запрос квитирования для установления соединения WebSocket с клиентом, сервер должен выполнить следующие операции.

Предполагая, что запрос установления связи от клиента является законным, то есть запрос на установление связи клиента соответствует определению RFC6455.

1,Сервер должен доказать клиенту, что он работает с протоколом WebSocket. Способ доказательства следующий

Случайным образом сгенерируйте 16-байтовый массив байтов на стороне клиента, зашифруйте массив байтов с помощью Base64 и заполните зашифрованную строку.Sec-WebSocket-Keyполя, как показано ниже

Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

Чтобы доказать, что сервер может работать с протоколом WebSocket, он долженSec-WebSocket-KeyВыполните следующие операции, чтобы получить его значение, котороеdGhlIHNhbXBsZSBub25jZQ==, что соответствует строке с магической строкой258EAFA5-E914-47DA-95CA-C5AB0DC85B11конкатенация, в результате чего получается следующая строка

dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11

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

0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea

Выполните алгоритм шифрования Base64 в приведенном выше массиве байтов, чтобы получить строкуs3pPLMBiTxaQ9kYGzzhZRbK+xOo=, затем запишите это значение в ответ на рукопожатиеSec-WebSocket-Acceptполе, что означает, что вы увидите следующие поля в ответе

Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Вышеприведенное взято из документа RFC6455. Если вы когда-либо обращались к стороннему интерфейсу (например, к официальному аккаунту определенного письма), он кажется вам знакомым?

Клиент проверит это значение, чтобы проверить, может ли сервер правильно обрабатывать протокол WebSocket.

Мы можем видеть, как контейнеры/фреймворки веб-приложений обрабатывают это поле.

игрок номер один Jetty

Ключевой класс, который обрабатывает протокол рукопожатияorg.eclipse.jetty.websocket.server.HandshakeRFC6455

public class HandshakeRFC6455 implements WebSocketHandshake
{
    /**
     * RFC 6455 - Sec-WebSocket-Version
     * 验证了上文的说法,即版本号必须为13
     * 没办法规矩是人定的,(ˉ▽ˉ;)...
     */
    public static final int VERSION = 13;

    @Override
    public void doHandshakeResponse(ServletUpgradeRequest request, ServletUpgradeResponse response) throws IOException
    {
        String key = request.getHeader("Sec-WebSocket-Key");
        if (key == null)
            throw new BadMessageException("Missing request header 'Sec-WebSocket-Key'");

        // build response
        response.setHeader("Upgrade", "WebSocket");
        response.addHeader("Connection", "Upgrade");
        //没错,关键类就在这儿 AcceptHash.hashKey(key)
        response.addHeader("Sec-WebSocket-Accept", AcceptHash.hashKey(key));

        request.complete();

        response.setStatusCode(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
        response.complete();
    }
}

AcceptHash.hashKey(key)

public class AcceptHash
{
    //魔法值,注意其编码的方式
    private static final byte[] MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(StandardCharsets.ISO_8859_1);
    
    public static String hashKey(String key)
    {
        try
        {
            //使用SHA1求对应字符串的散列值
            MessageDigest md = MessageDigest.getInstance("SHA1");
            //获取Sec-WebSocket-Key
            md.update(key.getBytes(StandardCharsets.UTF_8));
            //此法相当于将两个字符串连接在一起
            md.update(MAGIC);
            //digest()会获取经过SHA1算法计算之后的字节数组
            return Base64.getEncoder().encodeToString(md.digest());
        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }
    }
}

игрок номер два Express-ws

экспресс-зависимостиws, приведенный выше код находится вwebsocket-server.js

Оставьте небольшой вопрос, по сравнению с кодом JS, знаете ли вы, почему Java нужно указывать значение кодировки строки?

2,После обработки протокола WebSocket серверу необходимо обработать запрос расширения из запроса рукопожатия клиента, то есть обработатьSec-WebSocket-Extensionsполе. В этом поле указывается подключаемый модуль расширения, который клиент хочет, чтобы сервер загрузил, но фактическая загрузка подключаемого модуля зависит от сервера, и сервер укажет подключаемые модули, которые он поддерживает, в заголовке возвращаемого сообщения.

Например, клиент отправляет следующий запрос расширения подключаемого модуля, надеясь, что сервер загрузитpermessage-deflateа такжеclient_max_window_bitsплагин

Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

Если сервер поддерживает толькоpermessage-deflateплагин, то сервер вернется

Sec-WebSocket-Extensions: permessage-deflate

В последующем процессе связи и сервер, и клиент будут загружать толькоpermessage-deflateПлагин (об этом плагине будет упоминаться в процессе практики, который меня давно беспокоил)

3.Информировать клиента о подпротоколах, поддерживаемых сервером, то есть обрабатываяSec-WebSocket-Protocolполе и выберите один из поддерживаемых протоколов и верните его клиенту. Это обрабатывается следующим образом

    var protocol = req.headers['sec-websocket-protocol'];
    //如果存在sec-websocket-protocol字段
    if (protocol) {
      //客户端传过来的所有的协议
      protocol = protocol.trim().split(/ *, */);

      //
      // Optionally call external protocol selection handler.
      // handleProtocols 为钩子函数,即如果定义了此函数则将协议的选择交给此函数处理
      if (this.options.handleProtocols) {
        protocol = this.options.handleProtocols(protocol, req);
      } else {
        //否则,就默认呗,还能咋样
        protocol = protocol[0];
      }
      //如果选择到了协议,就返回Sec-WebSocket-Protocol
      if (protocol) {
        headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
        ws.protocol = protocol;
      }
    }

Это поле эквивалентно резервированию большего рабочего пространства для разработчиков.больше информации

Кроме того, ответ сервера ДОЛЖЕН содержать следующие поля и значения, указывающие на успешное соединение WebSocket.

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: WebSocket

упражняться

  • Напишите сервер и клиент WebSocket.
  • Откройте WireShark, если сервер локальный, он будет мониторить петлевую сеть, если нет, то мониторит соответствующую сетевую карту
  • Установите правило фильтрации, значение прослушивает соответствующий порт, порт этого тестового сервера — 8080.
tcp.port == 8080

Как показано на рисунке ниже, рукопожатие протокола WebSocket и пакеты связи, перехваченные WireShark. По сути, протокол рукопожатия можно рассматривать как процесс переговоров, то есть сервер и клиент сообщают друг другу, как они могут обрабатывать данные.

1. Клиент инициирует запрос рукопожатия

2. Сервер отвечает на запрос рукопожатия

3. Переключение протокола, в этом примере после того, как сервер завершит рукопожатие с клиентом, он сразу отправит клиенту часть данных, как показано на рисунке ниже, обратите внимание на стандартное содержимое в красном поле, на на этот раз протокол был преобразован в протокол WebSocket

Я полагаю, вы также обратили внимание на содержимое, отмеченное в первом желтом поле, которое является ключом к передаче данных по протоколу WebSocket. И именно поэтому мы используем WireShark для захвата пакетов, потому что Chrome не показывает нам основные детали, он просто сообщает нам, какие данные вернул сервер.

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

Чтобы действительно понять, почему протокол передачи данных WebSocket разработан таким образом, вы должны знать следующие концепции.

  • Протокол WebSocket — это протокол прикладного уровня, основанный на протоколе TCP.
  • TCP — это транспортный протокол, основанный на потоках байтов.
  • Потоки не имеют границ

Вышеуказанная концепция означает, что мы должны знать, какие границы данных. Другими словами, мы должны знать, какую форму разделить поток байта, чтобы получить полные данные.

Определение протокола связи передачи данных 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 ...                |
     +---------------------------------------------------------------+

Перед началом необходимоpayloadЧтобы объяснить, полезная нагрузка — это полезная нагрузка, которая понимает данные, которые мы хотим передать.

В следующей таблице объясняются различные поля WebSocket.

имя поля SIZE эффект
FIN 1 человек Указывает, является ли этот пакет данных последним пакетом этого сообщения.
RSV1, RSV2, RSV2 Один бит для каждого флага, всего три Эти три бита флага в основном связаны с расширением, и следующая практика даст подробное описание.
opcode 4 Указывает тип полезной нагрузки в этом пакете данных (т. е. полезную нагрузку, которую можно рассматривать как фактическое передаваемое содержимое). Поскольку он имеет 4 цифры, он поддерживает 16 типов содержимого.0001то есть текст,0002двоичные данные
MASK 1 человек Указывает, выполнила ли полезная нагрузка этого пакета данных операцию маскирования.Если этот бит равен 1, ключ маскирования будет работать.Как работает маска, будет объяснено на практике ниже.
Payload len 7 цифр Указывает длину полезной нагрузки. Поскольку это поле состоит из 7 бит, это поле поддерживает максимальную передачу 127 байт.
Extended Payload Length 16-битная или 64-битная Если значение поля Payload len равно126, то длина всей полезной нагрузки будет следующей16бит (т.е. 2 байта), если значение поля Payload len равно127Тогда длина всей полезной нагрузки будет следующей64биты (т.е. 8 байтов)
Masking-key 0 или 4 байта Это поле работает, если бит маски равен 1. Это поле в основном используется для выполнения операций с маской над данными.
Payload Data N байт Содержимое, которое будет передано в этом пакете данных, длина определяется параметрами Payload len и Extend payload len.

Теперь вы знаете, как протокол WebSocket определяет границы потоков?

упражняться

Лучший способ учиться — это практиковаться, давай, Ори, дай!

Тестовая среда: ошибка 10 + Chrome + Jetty

отправить данные

Сначала отправим сообщениеjuejin,kesan(длина 12) на сервер

Обратите внимание на следующую информацию

  • FIN = 1Поскольку сообщения, которое мы отправляем, достаточно для передачи в одном пакете, этот пакет данных также является последним/кадром, поэтому FIN = 1 подходит.
  • RSV1 = 1, RSV2 = 0, RSV3 = 0, Мы сказали выше, что эти три флага связаны с расширением, и в это время WireShark также напоминает нам, что эта передача включена.Pre-Message Compressedплагин
  • OPCODE = 0001, на этот раз полезной нагрузкой/передачей является текстовое сообщение
  • Payload length = 14Количество байтов в полезной нагрузке — 14. Вы нашли проблему?
  • Masking-key = 7eca6052Маска для этой полезной нагрузки7eca6052, маска вообще появляется в процессе отправки данных от клиента на сервер

Давайте Kangkang содержание этой передачи, вы нашли проблему?

Куда делись данные? Подождите, вы еще помните маску, посмотрим, как выглядят данные после снятия маски

Всё равно не то, после снятия маски всё равно не то, и длина не та, см. след.Decompressed payloadПока что? давайте зайдем и посмотрим

Наконец правильно! ! ! Теперь вы знаете, что случилось с этим переводом? Подумайте об этом некоторое время

RSV1 = 1Это означает, что Chrome включил плагин, и Jetty также поддерживает этот плагин, поэтому передача сообщений включила плагин, так какой же плагин включен? Фактически, приведенный выше протокол рукопожатия уже объяснил, что, когда клиент и сервер выполняют протокол рукопожатия, они согласовывают подключаемые модули, которые должны быть включены ими совместно, и передаютSec-WebSocket-Extensionsобъяснять. Плагины, включенные на этот раз,permessage-deflateПлагин для сжатия сообщений.

Но, к сожалению, сжатие сообщений, кажется, не работает, и они становятся длиннее ԾㅂԾ.

Проверим другим способом, отправим на сервер много 1 и посмотрим, что получится

О рев, работает.Видно, что длина данных до сжатия 41 байт, а сжатых данных всего 5 байт.Реально работает. (Реализация этого плагина выходит за рамки этой статьи)

Итак, я думаю, что мне делать с пакетом WebSocket, в котором не включены сжатые данные?

Есть ошибка 10 использования королевыBug多,PDF阅读器,EPUB阅读器EdgeбраузерДебют игроков

Правильно, Edge не поддерживаетpermessage-deflateПлагин сжатия данных

Эдж - несчастный ребенок. Его отец Майкрософт не дал ему подключаемый модуль. Это слишком жалко. Неудивительно, что он похитил хром из соседнего дома как своего сына.

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

Чтобы удалить маску, просто XOR 4 байта данных с маской См. следующий код, разделенный на 4 байта и менее 4 байтов.

    if (remaining >= 4 && (offset & 3) == 0)
    {
        payload.putInt(start, payload.getInt(start) ^ maskInt);
        start += 4;
        offset += 4;
    }
    else
    {
        payload.put(start, (byte)(payload.get(start) ^ maskBytes[offset & 3]));
        ++start;
        ++offset;
    }
Получить данные

Как показано на рисунке ниже, сообщение, отправляемое сервером клиенту, не имеет маски и подключаемого модуля, поэтому его RSV1, RSV2, RSV3 = 0.

Конечно, во время теста вы обнаружите, что браузер и сервер тайно играютpong,pong,pongигра, как показано ниже

По сути, это heartbeat-соединение между сервером и клиентом.Если вы не знаете, что такое heartbeat-соединение, рекомендуетсячитать

Суммировать

В протоколе WebSocket есть еще много деталей, о которых я не говорил. Я говорю только о том, что узнал (●ˇ∀ˇ●). Если вы действительно хотите понять детали дизайна протокола, это рекомендуется прочитать документ RFC6455.Ведь информация будет существовать после распространения степень искажения.

Что ты узнал?

  • Одним словом это流是没有边界(от Мастера Чжанабуклет), как только вы поймете эту концепцию и поймете другие протоколы прикладного уровня, основанные на транспортном протоколе TCP, это будет очень быстро.

  • Все методы оптимизации, применимые к Socket, в основном применимы и к WebSocket, например, NIO и AIO и т. д.

  • Действительно ли хорошо включать плагины сжатия данных? Не обязательно, или оценка.Ведь будь то сжатие данных или распаковка, это требует времени.Оно того стоит или нет зависит от бизнес-сценария.

  • Метод проверки личности, основанный на хэш-функции, проще говоря, заключается в объединении набора параметров и конкретной строки (ключа, магического числа) в согласованной форме для получения ее хэш-значения для верификатора для проверки идентификационной информации запрашивающей стороны.

Если кто-то читает, давайте поговорим о других аспектах, посмотрите спрос(^_^)