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

拼搏现实的明天。 2022-09-13 01:59 361阅读 0赞

文章目录

  • 简介
  • netty中的websocket
    • websocket的版本
    • FrameDecoder和FrameEncoder
    • WebSocketServerHandshaker
    • WebSocketFrame
  • 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类中,我们可以看到:

  1. UNKNOWN(AsciiString.cached(StringUtil.EMPTY_STRING)),
  2. V00(AsciiString.cached("0")),
  3. V07(AsciiString.cached("7")),
  4. V08(AsciiString.cached("8")),
  5. 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:

  1. WebSocket00FrameDecoder
  2. WebSocket00FrameEncoder
  3. WebSocket07FrameDecoder
  4. WebSocket07FrameEncoder
  5. WebSocket08FrameDecoder
  6. WebSocket08FrameEncoder
  7. WebSocket13FrameDecoder
  8. WebSocket13FrameEncoder

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

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

WebSocketServerHandshaker

netty提供了一个WebSocketServerHandshaker类来统一使用encoder和decoder的使用。netty提供一个工厂类WebSocketServerHandshakerFactory根据客户端请求header的websocket版本不同,来返回不同的WebSocketServerHandshaker。

  1. public WebSocketServerHandshaker newHandshaker(HttpRequest req) {
  2. CharSequence version = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_VERSION);
  3. if (version != null) {
  4. if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) {
  5. // Version 13 of the wire protocol - RFC 6455 (version 17 of the draft hybi specification).
  6. return new WebSocketServerHandshaker13(
  7. webSocketURL, subprotocols, decoderConfig);
  8. } else if (version.equals(WebSocketVersion.V08.toHttpHeaderValue())) {
  9. // Version 8 of the wire protocol - version 10 of the draft hybi specification.
  10. return new WebSocketServerHandshaker08(
  11. webSocketURL, subprotocols, decoderConfig);
  12. } else if (version.equals(WebSocketVersion.V07.toHttpHeaderValue())) {
  13. // Version 8 of the wire protocol - version 07 of the draft hybi specification.
  14. return new WebSocketServerHandshaker07(
  15. webSocketURL, subprotocols, decoderConfig);
  16. } else {
  17. return null;
  18. }
  19. } else {
  20. // Assume version 00 where version header was not specified
  21. return new WebSocketServerHandshaker00(webSocketURL, subprotocols, decoderConfig);
  22. }
  23. }

同样的, 我们可以看到,netty为websocket也定义了4种不同的WebSocketServerHandshaker。

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

  1. public final ChannelFuture handshake(Channel channel, FullHttpRequest req,
  2. HttpHeaders responseHeaders, final ChannelPromise promise)
  3. p.addBefore(ctx.name(), "wsencoder", newWebSocketEncoder());
  4. 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协议消息的格式:

  1. 0 1 2 3
  2. 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
  3. +-+-+-+-+-------+-+-------------+-------------------------------+
  4. |F|R|R|R| opcode|M| Payload len | Extended payload length |
  5. |I|S|S|S| (4) |A| (7) | (16/64) |
  6. |N|V|V|V| |S| | (if payload len==126/127) |
  7. | |1|2|3| |K| | |
  8. +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
  9. | Extended payload length continued, if payload len == 127 |
  10. + - - - - - - - - - - - - - - - +-------------------------------+
  11. | |Masking-key, if MASK set to 1 |
  12. +-------------------------------+-------------------------------+
  13. | Masking-key (continued) | Payload Data |
  14. +-------------------------------- - - - - - - - - - - - - - - - +
  15. : Payload Data continued ... :
  16. + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
  17. | Payload Data continued ... |
  18. +---------------------------------------------------------------+

rsv代表的是消息中的扩展字段,也就是RSV1,RSV2和RSV3。

除此之外就是ByteBuf的一些基本操作了。

WebSocketFrame是一个抽象类,它的具体实现类有下面几种:

  1. BinaryWebSocketFrame
  2. CloseWebSocketFrame
  3. ContinuationWebSocketFrame
  4. PingWebSocketFrame
  5. PongWebSocketFrame
  6. 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:

  1. pipeline.addLast(new WebSocketServerHandler());

这个WebSocketServerHandler需要做什么事情呢?

它需要同时处理普通的HTTP请求和webSocket请求。

这两种请求可以通过接收到的msg类型的不同来进行判断:

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

在客户端进行websocket连接之前,需要借用当前的channel通道,开启handleshake:

  1. // websocket握手
  2. WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
  3. getWebSocketLocation(req), null, true, 5 * 1024 * 1024);
  4. handshaker = wsFactory.newHandshaker(req);
  5. if (handshaker == null) {
  6. WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
  7. } else {
  8. handshaker.handshake(ctx.channel(), req);
  9. }

我们得到handshaker之后,就可以对后续的WebSocketFrame进行处理:

  1. private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
  2. // 处理各种websocket的frame信息
  3. if (frame instanceof CloseWebSocketFrame) {
  4. handshaker.close(ctx, (CloseWebSocketFrame) frame.retain());
  5. return;
  6. }
  7. if (frame instanceof PingWebSocketFrame) {
  8. ctx.write(new PongWebSocketFrame(frame.content().retain()));
  9. return;
  10. }
  11. if (frame instanceof TextWebSocketFrame) {
  12. // 直接返回
  13. ctx.write(frame.retain());
  14. return;
  15. }
  16. if (frame instanceof BinaryWebSocketFrame) {
  17. // 直接返回
  18. ctx.write(frame.retain());
  19. }
  20. }

这里我们只是机械的返回消息,大家可以根据自己业务逻辑的不同,对消息进行解析。

有了服务器端,客户端该怎么连接呢?很简单首选构造WebSocket对象,然后处理各种回调即可:

  1. socket = new WebSocket("ws://127.0.0.1:8000/websocket");
  2. socket.onmessage = function (event) {
  3. }
  4. socket.onopen = function(event) {
  5. };
  6. socket.onclose = function(event) {
  7. };

总结

以上就是使用netty搭建websocket服务器的完整流程,本文中的服务器可以同时处理普通HTTP请求和webSocket请求,但是稍显复杂,有没有更加简单的方式呢?敬请期待。

本文的例子可以参考:learn-netty4

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

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

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

发表评论

表情:
评论列表 (有 0 条评论,361人围观)

还没有评论,来说两句吧...

相关阅读

    相关 NettyWebSocket

    WebSocket 简介 WebSocket 协议最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于[服务器推送技