Серия Netty: используйте netty для создания сервера веб-сокетов

Java Netty WebSocket
Серия Netty: используйте netty для создания сервера веб-сокетов

Мало знаний, большой вызов! Эта статья участвует в "Необходимые знания для программистов«Творческая деятельность

Введение

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

Поскольку websocket настолько хорош, как использовать websocket в netty?

веб-сокет в netty

Хотя веб-сокет — это отдельный протокол, полностью отличный от протокола HTTP, он все равно помещается в пакет http в netty. Вспомним поддержку различных протоколов в netty. Если вы хотите поддерживать этот протокол, вам обязательно нужен декодер и кодировщик для кодирования и декодирования протокола. Преобразование передаваемых данных из ByteBuf в тип протокола или преобразование типа протокола в ByteBuf.

Это основной принцип работы netty и основа для последующих пользовательских расширений netty.

Так что насчет веб-сокетов?

версия веб-сокета

Как протокол, WebSocket не появился из воздуха, и он достиг сегодняшнего протокола WebSocket благодаря непрерывному развитию. Мы не будем углубляться в конкретную историю развития webSocket. Давайте сначала посмотрим на различные версии WebSocket, предоставляемые netty.

В классе WebSocketVersion мы видим:

UNKNOWN(AsciiString.cached(StringUtil.EMPTY_STRING)),

    V00(AsciiString.cached("0")),

    V07(AsciiString.cached("7")),

    V08(AsciiString.cached("8")),

    V13(AsciiString.cached("13"));

WebSocketVersion — это тип перечисления, который определяет версии веб-сокета 4. Помимо НЕИЗВЕСТНО, мы можем видеть, что версии веб-сокета — 0, 7, 8 и 13.

FrameDecoder и FrameEncoder

Мы знаем, что сообщения веб-сокетов передаются через фреймы, потому что разные версии веб-сокетов влияют на формат фрейма. Поэтому нам нужны разные FrameDecoder и FrameEncoder для преобразования между WebSocketFrame и ByteBuf.

Поскольку существует четыре версии websocket, соответственно четыре версии декодера и кодировщика:

WebSocket00FrameDecoder
WebSocket00FrameEncoder
WebSocket07FrameDecoder
WebSocket07FrameEncoder
WebSocket08FrameDecoder
WebSocket08FrameEncoder
WebSocket13FrameDecoder
WebSocket13FrameEncoder

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

Друзья, знакомые с netty, должны знать, что и кодировщик, и декодер используются для преобразования сообщений в канале. Итак, какова поддержка веб-сокета в netty?

WebSocketServerHandshaker

Netty предоставляет класс WebSocketServerHandshaker для унификации использования кодировщика и декодера. Netty предоставляет фабричный класс WebSocketServerHandshakerFactory для возврата различных WebSocketServerHandshaker в соответствии с запрошенной клиентом версией заголовка веб-сокета.

public WebSocketServerHandshaker newHandshaker(HttpRequest req) {

        CharSequence version = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_VERSION);
        if (version != null) {
            if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) {
                // Version 13 of the wire protocol - RFC 6455 (version 17 of the draft hybi specification).
                return new WebSocketServerHandshaker13(
                        webSocketURL, subprotocols, decoderConfig);
            } else if (version.equals(WebSocketVersion.V08.toHttpHeaderValue())) {
                // Version 8 of the wire protocol - version 10 of the draft hybi specification.
                return new WebSocketServerHandshaker08(
                        webSocketURL, subprotocols, decoderConfig);
            } else if (version.equals(WebSocketVersion.V07.toHttpHeaderValue())) {
                // Version 8 of the wire protocol - version 07 of the draft hybi specification.
                return new WebSocketServerHandshaker07(
                        webSocketURL, subprotocols, decoderConfig);
            } else {
                return null;
            }
        } else {
            // Assume version 00 where version header was not specified
            return new WebSocketServerHandshaker00(webSocketURL, subprotocols, decoderConfig);
        }
    }

Точно так же мы можем видеть, что netty также определяет 4 разных WebSocketServerHandshakers для веб-сокетов.

Метод рукопожатия определяется в WebSocketServerHandshaker путем передачи канала и добавления к нему кодировщика и декодера.

public final ChannelFuture handshake(Channel channel, FullHttpRequest req,
                                            HttpHeaders responseHeaders, final ChannelPromise promise) 

            p.addBefore(ctx.name(), "wsencoder", newWebSocketEncoder());
            p.addBefore(ctx.name(), "wsdecoder", newWebsocketDecoder());

Два добавленных newWebSocketEncoder и newWebsocketDecoder определены в конкретной реализации каждого WebSocketServerHandshaker.

WebSocketFrame

Все кодирование и декодирование конвертируются в WebSocketFrame и ByteBuf. WebSocketFrame наследуется от DefaultByteBufHolder, указывая, что это контейнер ByteBuf. Помимо хранения ByteBuf, он имеет два дополнительных свойства: finalFragment и rsv.

finalFragment указывает, является ли кадр последним. Для сообщений с большими объемами данных сообщение будет разбито на разные фреймы, это свойство особенно полезно.

Давайте еще раз посмотрим на формат сообщения протокола 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 ...                |
     +---------------------------------------------------------------+

rsv представляет поля расширения в сообщении, то есть RSV1, RSV2 и RSV3.

Кроме того, есть некоторые основные операции ByteBuf.

WebSocketFrame — это абстрактный класс, и его конкретные классы реализации следующие:

BinaryWebSocketFrame
CloseWebSocketFrame
ContinuationWebSocketFrame
PingWebSocketFrame
PongWebSocketFrame
TextWebSocketFrame

BinaryWebSocketFrame и TextWebSocketFrame хорошо изучены, они представляют два способа передачи сообщений.

CloseWebSocketFrame — это фрейм, представляющий закрытие соединения. ContinuationWebSocketFrame представляет собой представление более чем одного кадра в сообщении.

PingWebSocketFrame и PongWebSocketFrame — это два специальных фрейма, которые в основном используются для обнаружения сервера и клиента.

Эти фреймы находятся во взаимно однозначном соответствии с типами сообщений веб-сокетов.Очень полезно понимать типы сообщений веб-сокетов и понимать эти классы фреймов.

Использование веб-сокетов в netty

После разговора о принципах и классах реализации множества веб-сокетов следующим шагом будет собственно бой.

В этом примере мы используем netty для создания сервера веб-сокетов, а затем используем клиент браузера для доступа к серверу.

Процесс создания сервера websocket ничем не отличается от обычного сервера netty. Просто в ChannelPipeline нужно добавить кастомный WebSocketServerHandler:

pipeline.addLast(new WebSocketServerHandler());

Что должен делать этот WebSocketServerHandler?

Он должен обрабатывать как обычные HTTP-запросы, так и запросы WebSocket.

Об этих двух запросах можно судить по разнице в типе полученного msg:

    public void channelRead0(ChannelHandlerContext ctx, Object msg) throws IOException {
        //根据消息类型,处理两种不同的消息
        if (msg instanceof FullHttpRequest) {
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {
            handleWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }

Прежде чем клиент установит соединение через веб-сокет, ему необходимо заимствовать текущий канал и открыть хендлшейк:

        // websocket握手
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
                getWebSocketLocation(req), null, true, 5 * 1024 * 1024);
        handshaker = wsFactory.newHandshaker(req);
        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            handshaker.handshake(ctx.channel(), req);
        }

После того, как мы получим рукопожатие, мы можем обработать следующий WebSocketFrame:

private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {

        // 处理各种websocket的frame信息
        if (frame instanceof CloseWebSocketFrame) {
            handshaker.close(ctx, (CloseWebSocketFrame) frame.retain());
            return;
        }
        if (frame instanceof PingWebSocketFrame) {
            ctx.write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        if (frame instanceof TextWebSocketFrame) {
            // 直接返回
            ctx.write(frame.retain());
            return;
        }
        if (frame instanceof BinaryWebSocketFrame) {
            // 直接返回
            ctx.write(frame.retain());
        }
    }

Здесь мы просто возвращаем сообщение механически, вы можете разобрать сообщение в соответствии со своей бизнес-логикой.

Со стороны сервера, как должен подключаться клиент? Сначала очень просто создать объект WebSocket, а затем обрабатывать различные обратные вызовы:

socket = new WebSocket("ws://127.0.0.1:8000/websocket");
socket.onmessage = function (event) { 

}
socket.onopen = function(event) {
        };
socket.onclose = function(event) {
        };

Суммировать

Выше приведен полный процесс использования netty для создания сервера веб-сокетов.Сервер в этой статье может обрабатывать обычные HTTP-запросы и запросы веб-сокетов одновременно, но это немного сложно.Есть ли более простой способ? Быть в курсе.

Примеры этой статьи могут относиться к:learn-netty4

Эта статья была включена вWoohoo.Floyd Press.com/23-Netty-Towering…

Самая популярная интерпретация, самая глубокая галантерея, самые краткие уроки и множество трюков, о которых вы не знаете, ждут вас!

Добро пожаловать, чтобы обратить внимание на мой официальный аккаунт: «Программируйте эти вещи», разбирайтесь в технологиях, лучше поймите себя!