写点什么

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:


WebSocket00FrameDecoderWebSocket00FrameEncoderWebSocket07FrameDecoderWebSocket07FrameEncoderWebSocket08FrameDecoderWebSocket08FrameEncoderWebSocket13FrameDecoderWebSocket13FrameEncoder 至于每个版本之间的 frame 有什么区别,我们这里就不细讲了,感兴趣的朋友可以关注我的后续文章。


熟悉 netty 的朋友应该都知道,不管是 encoder 还是 decoder 都是作用在 channel 中对消息进行转换的。那么在 netty 中对 websocket 的支持是怎么样的呢?


WebSocketServerHandshakernetty 提供了一个 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 是一个抽象类,它的具体实现类有下面几种:


BinaryWebSocketFrameCloseWebSocketFrameContinuationWebSocketFramePingWebSocketFramePongWebSocketFrameTextWebSocketFrameBinaryWebSocketFrame 和 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/


最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!


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

发布于: 刚刚阅读数: 2
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
netty系列之:使用netty搭建websocket服务器