netty系列之:使用netty搭建websocket服务器
文章目录
- 简介
- 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类中,我们可以看到:
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 specifiedreturn 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 30 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/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
netty系列之:使用netty搭建websocket服务器相关推荐
- 【Netty系列】使用Netty搭建WebSocket服务器
使用Netty搭建WebSocket服务器 引入netty的依赖 <dependency><groupId>io.netty</groupId><artifa ...
- 【Netty系列_3】Netty源码分析之服务端channel
highlight: androidstudio 前言 学习源码要有十足的耐性!越是封装完美的框架,内部就越复杂,源码很深很长!不过要抓住要点分析,实在不行多看几遍,配合debug,去一窥优秀框架的精 ...
- 在IIS上搭建WebSocket服务器(三)
在IIS上搭建WebSocket服务器(三) 原文:在IIS上搭建WebSocket服务器(三) 编写客户端代码 1.新建一个*.html文件. ws = new WebSocket('ws://19 ...
- websocket 学习--简单使用,nodejs搭建websocket服务器,到模拟股票,到实现聊天室
websocket简介: WebSocket协议是 HTML5 开始提供的一种基于TCP的一种新的全双工通讯的网络通讯协议.它允许服务器主动发送信息给客户端. 和http协议的不同?? HTTP 协议 ...
- 项目实战-物联网平台搭建-搭建websocket服务器(三)
项目目前是有手机app 做客户端,通过app订阅主题并发送消息,mqtt服务器会将数据转存如mysql数据库中,然后再由ws服务端发送到前端展示 下边是大二写的一个mqtt通信app 客户端给温度主题 ...
- netty系列之:请netty再爱UDT一次
文章目录 简介 netty对UDT的支持 搭建一个支持UDT的netty服务 异常来袭 TypeUDT和KindUDT 构建ChannelFactory SelectorProviderUDT 使用U ...
- Netty系列二、Netty原理篇
文章目录 一.Netty概述 二.Netty整体架构设计 1.Reactor模型 2.Reactor模型分类 2.1 单Reactor单线程 2.2 单Reactor多线程 2.3 多Reactor多 ...
- 使用libwebsocket搭建websocket服务器实例
webcomm与前端的通讯-websocket服务器操作流程 #include <libwebsockets.h> #include <pthread.h> #include ...
- netty系列之:使用netty搭建websocket客户端
文章目录 简介 浏览器客户端 netty对websocket客户端的支持 WebSocketClientHandshaker WebSocketClientCompressionHandler net ...
最新文章
- Eigen::Matrix
- Chrome 科研神器!论文代码快速复现
- console 一行_你还在用 console.log 调试?
- 算法:前K个最大的元素
- 添加公共引用目录_原来Word还可以自动生成图片和图表目录!
- mysql索引为啥要选择B+树 (下)
- R语言预测初步(R语言预测实战-节选)
- matlab 向量转置,matlab中向量和矩阵怎么转置 值得收藏
- EXCEL数组公式(6),注意聚合函数在数组公式中的用法
- 为报复老东家,程序员编码给自己转账553笔,金额超21万元
- IOS学习之Segue
- 如何使用计算机来线性拟合,非线性数据拟合
- Tikhonov regularization 吉洪诺夫正则化
- html中自动换行标记[转]
- D.背单词的小智(二分)
- 一年降本 40%:基于云服务的技术成本精细化运营策略
- PHP充值怎么打折,教大家如何用PHP语言写一个简单的商品打折小程序!
- Java项目-基于SSM实现公交路线管理系统
- Kotlin 协程探索
- iOS摸鱼周报 第二期
热门文章
- 自学JAVA5.18
- rss C语言,Android内存:VSS/RSS/PSS/USS介绍
- deque,list,queue,priority_queue
- 网上一个仿TP挂钩内核的源码
- Win32多线程编程(5) — 线程局部存储
- CrashFinder,找到崩溃代码行
- 经典|Linux:为什么性能工具需要 BPF 技术(送多本)
- 深入理解RCU | RCU源码剖析
- 知乎高赞:从源码层,拆解OracleJDK和OpenJDK有什么区别?网友:不愧是大神的回答~...
- 网关Spring Cloud Gateway科普