Netty网络编程(六):搭建websocket服务器

简介: Netty网络编程(六):搭建websocket服务器

一、简介

websocket是一个优秀的协议,它是建立在TCP基础之上的,兼容HTTP的网络协议。通过Websocket我们可以实现客户端和服务器端的即时通讯,免除了客户端多次轮循带来的性能损耗。


既然websocket这么优秀,那么怎么在netty中使用websocket呢?


92.png

二、netty中的websocket


虽然websocket是一个单独的和HTTP协议完全不同的协议,但是在netty中还是将其放到了http包中。我们回想一下netty中对于各种协议的支持。如果要支持这种协议,肯定需要一个decoder和encoder编码和解码器用于对协议进行编解码。将传输的数据从ByteBuf转换到协议类型,或者将协议类型转换成为ByteBuf。


这是netty的工作核心原理,也是后续自定义netty扩展的基础。


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


1、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这几个。


2、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的支持是怎么样的呢?


3、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的具体实现中定义的。


4、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请求。

相关文章
|
11天前
|
编解码 分布式计算 网络协议
Netty高性能网络框架(一)
Netty高性能网络框架(一)
|
2月前
|
缓存 应用服务中间件 nginx
Web服务器的缓存机制与内容分发网络(CDN)
【8月更文第28天】随着互联网应用的发展,用户对网站响应速度的要求越来越高。为了提升用户体验,Web服务器通常会采用多种技术手段来优化页面加载速度,其中最重要的两种技术就是缓存机制和内容分发网络(CDN)。本文将深入探讨这两种技术的工作原理及其实现方法,并通过具体的代码示例加以说明。
87 1
|
2月前
|
监控 安全 数据挖掘
网络游戏服务器如何有效防护DDoS与CC攻击
随着网络游戏行业的蓬勃发展,其背后的服务器架构日益复杂,同时也面临着前所未有的网络安全威胁。其中,分布式拒绝服务(DDoS)和CC(Challenge Collapsar,一种针对网页的攻击方式)攻击尤为突出,它们通过大量伪造请求或恶意流量,使服务器资源耗尽,导致服务中断或响应缓慢。因此,保障网络游戏服务器的安全,有效防护DDoS与CC攻击,已成为游戏行业亟待解决的问题。
|
17天前
|
存储 弹性计算 测试技术
阿里云服务器实例规格vCPU、内存、网络带宽、网络收发包PPS、连接数等性能指标详解
阿里云服务器ECS实例可以分为多种实例规格族。根据CPU、内存等配置,一种实例规格族又分为多种实例规格。而实例规格又包含vCPU、处理器、内存、vTPM、本地存储、网络带宽、网络收发包PPS、连接数、弹性网卡、云盘带宽、云盘IOPS等指标,本文为大家详细介绍实例规格的这些指标,以供大家了解和选择。
阿里云服务器实例规格vCPU、内存、网络带宽、网络收发包PPS、连接数等性能指标详解
|
7天前
|
存储 关系型数据库 MySQL
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
53 5
|
11天前
|
数据安全/隐私保护
Haskell网络编程:代理服务器的高级使用技巧
Haskell网络编程:代理服务器的高级使用技巧
|
10天前
|
存储 机器人 Linux
Netty(二)-服务端网络编程常见网络IO模型讲解
Netty(二)-服务端网络编程常见网络IO模型讲解
|
23天前
|
存储 运维 网络协议
运维的基本概念:服务器和网络基础知识
运维的基本概念:服务器和网络基础知识
45 0
运维的基本概念:服务器和网络基础知识
|
2月前
|
API Windows
揭秘网络通信的魔法:Win32多线程技术如何让服务器化身超级英雄,同时与成千上万客户端对话!
【8月更文挑战第16天】在网络编程中,客户/服务器模型让客户端向服务器发送请求并接收响应。Win32 API支持在Windows上构建此类应用。首先要初始化网络环境并通过`socket`函数创建套接字。服务器需绑定地址和端口,使用`bind`和`listen`函数准备接收连接。对每个客户端调用`accept`函数并在新线程中处理。客户端则通过`connect`建立连接,双方可通过`send`和`recv`交换数据。多线程提升服务器处理能力,确保高效响应。
40 6
|
2月前
|
缓存 安全 Linux
本地YUM源大揭秘:搭建您自己的Linux软件宝库,从此告别网络依赖!一文掌握服务器自给自足的终极技能!
【8月更文挑战第13天】在Linux中,YUM是一款强大的软件包管理工具,可自动处理依赖关系。为适应离线或特定安全需求,本指南教你搭建本地YUM源。首先创建存放软件包的`localrepo`目录,复制`.rpm`文件至其中。接着,安装并运用`createrepo`生成仓库元数据。随后配置新的`.repo`文件指向该目录,并禁用GPG检查。最后,清理并重建YUM缓存,即可启用本地YUM源进行软件搜索与安装,适用于网络受限环境。
87 3
下一篇
无影云桌面