简介

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的反对是怎么样的呢?

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

BinaryWebSocketFrameCloseWebSocketFrameContinuationWebSocketFramePingWebSocketFramePongWebSocketFrameTextWebSocketFrame

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/

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

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