Introduction

Websocket is an excellent protocol. It is based on TCP and is compatible with the HTTP network protocol. Through Websocket, we can realize the instant communication between the client and the server, avoiding the performance loss caused by multiple rounds of the client.

Since websocket is so good, how to use websocket in netty?

websocket in netty

Although websocket is a separate protocol completely different from the HTTP protocol, it is still put in the http package in netty. Let us recall the support for various protocols in netty. If you want to support this protocol, you must definitely need a decoder and encoder encoder and decoder to encode and decode the protocol. Convert the transmitted data from ByteBuf to the protocol type, or convert the protocol type to ByteBuf.

This is the core principle of netty's work and the basis for subsequent custom netty extensions.

So what is it like for websocket?

version of websocket

As a protocol, WebSocket naturally did not come out of thin air. Through continuous development, it has reached the WebSocket protocol today. We will not go into the specific development history of webSocket. Let's first look at the various WebSocket versions provided by netty.

In the WebSocketVersion class, we can see:

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

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

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

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

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

WebSocketVersion is an enumerated type, which defines 4 versions of websocket. In addition to UNKNOWN, we can see that the versions of websocket are 0, 7, 8, 13.

FrameDecoder and FrameEncoder

We know that websocket messages are transmitted through the frame, because different websocket versions affect the format of the frame. So we need different FrameDecoder and FrameEncoder to convert between WebSocketFrame and ByteBuf.

Since there are four versions of websocket, there are four corresponding versions of decoder and encoder:

WebSocket00FrameDecoder
WebSocket00FrameEncoder
WebSocket07FrameDecoder
WebSocket07FrameEncoder
WebSocket08FrameDecoder
WebSocket08FrameEncoder
WebSocket13FrameDecoder
WebSocket13FrameEncoder

As for the difference between the frames of each version, we won't go into details here, and interested friends can pay attention to my follow-up article.

Friends who are familiar with netty should know that both encoder and decoder are used to convert messages in the channel. So what is the support for websocket in netty?

WebSocketServerHandshaker

netty provides a WebSocketServerHandshaker class to uniformly use the encoder and decoder. Netty provides a factory class WebSocketServerHandshakerFactory to return different WebSocketServerHandshaker according to the different websocket version of the header requested by the client.

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);
        }
    }

Similarly, we can see that netty also defines 4 different WebSocketServerHandshaker for websocket.

The handleshake method is defined in WebSocketServerHandshaker, and the encoder and decoder are added to the channel by passing in

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());

The two added newWebSocketEncoder and newWebsocketDecoder are defined in the specific implementation of each WebSocketServerHandshaker.

WebSocketFrame

All ecode and decode are converted in WebSocketFrame and ByteBuf. WebSocketFrame inherits from DefaultByteBufHolder, which means it is a ByteBuf container. In addition to storing ByteBuf, it has two additional attributes, finalFragment and rsv.

finalFragment indicates whether the frame is the last Frame. For messages with a large amount of data, the message will be split into different frames, which is particularly useful.

Let's look at the format of the websocket protocol message:


      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 represents the extended fields in the message, that is, RSV1, RSV2, and RSV3.

In addition, there are some basic operations of ByteBuf.

WebSocketFrame is an abstract class, and its specific implementation classes are as follows:

BinaryWebSocketFrame
CloseWebSocketFrame
ContinuationWebSocketFrame
PingWebSocketFrame
PongWebSocketFrame
TextWebSocketFrame

BinaryWebSocketFrame and TextWebSocketFrame are well understood, they represent two ways of message transmission.

CloseWebSocketFrame is a frame that represents closing the connection. ContinuationWebSocketFrame represents the representation of more than one frame in the message.

PingWebSocketFrame and PongWebSocketFrame are two special frames, they are mainly used for server and client detection.

These frames are in one-to-one correspondence with the message types of Websocket. After understanding the message types of websocket, it is very helpful to understand these frame classes accordingly.

Use websocket in netty

After talking about the principles and implementation of so many websockets, the next step is the actual combat.

In this example, we use netty to create a websocket server, and then use the browser client to access the server.

The process of creating a websocket server is no different from a normal netty server. Only in ChannelPipeline, you need to add a custom WebSocketServerHandler:

pipeline.addLast(new WebSocketServerHandler());

What does this WebSocketServerHandler need to do?

It needs to process normal HTTP requests and webSocket requests at the same time.

These two requests can be judged by the difference of the received msg type:

    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);
        }
    }

Before the client connects to the websocket, it needs to borrow the current channel and turn on the handlehake:

        // 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);
        }

After we get the handshaker, we can process the subsequent 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());
        }
    }

Here we are just returning the message mechanically. You can parse the message according to your business logic.

With the server, how should the client connect? It is very simple to first construct a WebSocket object, and then handle various callbacks:

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

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

Summarize

The above is the complete process of using netty to build a websocket server. The server in this article can handle ordinary HTTP requests and webSocket requests at the same time, but it is a bit more complicated. Is there a simpler way? Stay tuned.

For the examples in this article, please refer to: learn-netty4

This article has been included in http://www.flydean.com/23-netty-websocket-server/

The most popular interpretation, the most profound dry goods, the most concise tutorial, and many tips you don't know are waiting for you to discover!

Welcome to pay attention to my official account: "Program those things", know technology, know you better!


flydean
890 声望432 粉丝

欢迎访问我的个人网站:www.flydean.com