乐趣区

关于netty:netty系列之使用netty搭建websocket服务器

简介

websocket 是一个优良的协定,它是建设在 TCP 根底之上的,兼容 HTTP 的网络协议。通过 Websocket 咱们能够实现客户端和服务器端的即时通讯,罢黜了客户端屡次轮循带来的性能损耗。

既然 websocket 这么优良,那么怎么在 netty 中应用 websocket 呢?

netty 中的 websocket

尽管 websocket 是一个独自的和 HTTP 协定齐全不同的协定,然而在 netty 中还是将其放到了 http 包中。咱们回忆一下 netty 中对于各种协定的反对。如果要反对这种协定,必定须要一个 decoder 和 encoder 编码和解码器用于对协定进行编解码。将传输的数据从 ByteBuf 转换到协定类型,或者将协定类型转换成为 ByteBuf。

这是 netty 的工作外围原理,也是后续自定义 netty 扩大的根底。

那么对于 websocket 来说,是怎么样的呢?

websocket 的版本

WebSocket 作为一种协定,天然不是凭空而来的,通过一直的倒退才到了明天的 WebSocket 协定。具体的 webSocket 的发展史咱们就不去深究了。咱们先看下 netty 提供的各种 WebSocket 的版本。

在 WebSocketVersion 类中,咱们能够看到:

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

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

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

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

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

WebSocketVersion 是一个枚举类型,它外面定义了 websocket 的 4 个版本,除了 UNKNOWN 之外,咱们能够看到 websocket 的版本有 0,7,8,13 这几个。

FrameDecoder 和 FrameEncoder

咱们晓得 websocket 的音讯是通过 frame 来传递的,因为不同 websocket 的版本影响到的是 frame 的格局的不同。所以咱们须要不同的 FrameDecoder 和 FrameEncoder 来在 WebSocketFrame 和 ByteBuf 之间进行转换。

既然 websocket 有四个版本,那么绝对应的就有 4 个版本的 decoder 和 encoder:

WebSocket00FrameDecoder
WebSocket00FrameEncoder
WebSocket07FrameDecoder
WebSocket07FrameEncoder
WebSocket08FrameDecoder
WebSocket08FrameEncoder
WebSocket13FrameDecoder
WebSocket13FrameEncoder

至于每个版本之间的 frame 有什么区别,咱们这里就不细讲了,感兴趣的敌人能够关注我的后续文章。

相熟 netty 的敌人应该都晓得,不论是 encoder 还是 decoder 都是作用在 channel 中对音讯进行转换的。那么在 netty 中对 websocket 的反对是怎么样的呢?

WebSocketServerHandshaker

netty 提供了一个 WebSocketServerHandshaker 类来对立应用 encoder 和 decoder 的应用。netty 提供一个工厂类 WebSocketServerHandshakerFactory 依据客户端申请 header 的 websocket 版本不同,来返回不同的 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 为 websocket 也定义了 4 种不同的 WebSocketServerHandshaker。

WebSocketServerHandshaker 中定义了 handleshake 办法,通过传入 channel,并向其增加 encoder 和 decoder

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

所有的 ecode 和 decode 都是在 WebSocketFrame 和 ByteBuf 中进行转换。WebSocketFrame 继承自 DefaultByteBufHolder,示意它是一个 ByteBuf 的容器。除了保留有 ByteBuf 之外,它还有两个额定的属性,别离是 finalFragment 和 rsv。

finalFragment 示意该 frame 是不是最初一个 Frame。对于大数据量的音讯来说,会将音讯拆分成为不同的 frame,这个属性特地有用。

咱们再看一下 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 是代表敞开连贯的 frame。ContinuationWebSocketFrame 示意音讯中多于一个 frame 的示意。

而 PingWebSocketFrame 和 PongWebSocketFrame 是两个非凡的 frame,他们次要用来做服务器和客户端的探测。

这些 frame 都是跟 Websocket 的音讯类型一一对应的,了解了 websocket 的音讯类型,对应了解这些 frame 类还是很有帮忙的。

netty 中应用 websocket

讲了这么多 websocket 的原理和实现类,接下来就是实战了。

在这个例子中,咱们应用 netty 创立一个 websocket server,而后应用浏览器客户端来对 server 进行拜访。

创立 websocket server 和一般 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 连贯之前,须要借用以后的 channel 通道,开启 handleshake:

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

咱们失去 handshaker 之后,就能够对后续的 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 搭建 websocket 服务器的残缺流程,本文中的服务器能够同时解决一般 HTTP 申请和 webSocket 申请,然而稍显简单,有没有更加简略的形式呢?敬请期待。

本文的例子能够参考:learn-netty4

本文已收录于 http://www.flydean.com/23-netty-websocket-server/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

退出移动版