上一节,我们主要讲解了 Http 协议,本节我们来讲解 Netty 支持的另外一个 WebSocket 协议,WebSocket 是 HTML5 开始支持的全双工协议。
在真实的开发当中,其实 WebSocket 使用非常的广泛,特别是在目前前端技术越来越完善的情况下,很多应用都是基于前端技术去实现,比如:APP、小程序等主流应用,前端的技术完全可以开发出类似原生技术一样的产品,并且开发效率上比原生更加的快、用户体验更好。
这些应用涉及通信、聊天、推送等业务,则可以使用 WebSocket 来实现,因此,WebSocket 已经是目前非常主流的浏览器和服务端建立长连接的通信技术。
WebSocket 协议架构图如下所示:
Netty 虽然可以开发客户端和服务端的应用,但是 Netty 和 WebSocket 结合则是另外一个不同的应用方向。
应用方向一: 纯后端的应用,比如:框架、中间件通信等,则完全可以使用 Netty 来开发客户端和服务端,并且双方通过 TCP 协议来进行通信。
应用方向二: 前端(浏览器)和服务端之间通信,并且想实现类似长连接的效果,那么 WebSocket 和 Netty 则是主流,并且这部分的应用场景非常的广泛和运用非常的多。
因此,我们在学习完本章内容之后,我们就可以掌握这类的需求的开发(比如:聊天、消息推送),自己可以尝试去开发一款小程序在线聊天系统。
当真实的开发当中,也许很多人直接使用 Spring 封装的 WebSocket 或者其它第三方的框架(比如:netty-socketio)去实现。本节我们主要讲解原生 Netty 来开发 WebSocket 协议格式的数据请求,毕竟会用和知道知其所以然还是有区别的。
创建 WebSocket 对象,第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。
var Socket = new WebSocket(url, [protocol] );
代码块预览 复制
- 1
WebSocket 事件
事件 | 事件处理程序 | 描述 |
---|---|---|
open | Socket.onopen | 连接建立时触发 |
message | Socket.onmessage | 客户端接收服务端数据时触发 |
error | Socket.onerror | 通信发生错误时触发 |
close | Socket.onclose | 连接关闭时触发 |
本节,我们主要开发一个 Demo,主要功能如下:
- 前端页面(html)启动的时候,连接 WebSocket 服务端;
- 服务端往前端每隔 5s 推送一条消息。
环境搭建步骤:
- 创建一个 Maven 项目;
- 导入 Netty 坐标。
实例:
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.6.Final</version> </dependency>
代码块预览 复制
- 1
- 2
- 3
- 4
- 5
public class MyWebSocketServer { public static void main(String[] args) throws Exception{ EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup); serverBootstrap.channel(NioServerSocketChannel.class); serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); //因为基于http协议,使用http的编码和解码器 pipeline.addLast(new HttpServerCodec()); //是以块方式写,添加ChunkedWriteHandler处理器 pipeline.addLast(new ChunkedWriteHandler()); //http数据在传输过程中是分段, HttpObjectAggregator ,就是可以将多个段聚合 pipeline.addLast(new HttpObjectAggregator(8192)); //将 http协议升级为 ws协议 , 保持长连接 pipeline.addLast(new WebSocketServerProtocolHandler("/hello2")); //自定义的handler ,处理业务逻辑 pipeline.addLast(new MyWebSocketHandler()); } }); //启动服务器 ChannelFuture channelFuture = serverBootstrap.bind(7000).sync(); channelFuture.channel().closeFuture().sync(); }finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
代码块预览 复制
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
代码说明:
- WebSocket 比 TCP 和 Http 协议都稍微复杂有点,它其实是 TCP 和 Http 协议的结合,首先是连接之前发送的是 Http 协议请求,但是新增 Http 所没有的附加头信息
Upgrade: WebSocket
表明是一个申请协议升级的 Http 请求。其次,真正建立连接之后,其实底层是 TCP 协议长连接; HttpServerCodec
将请求和应答消息解码为 HTTP 消息;ChunkedWriteHandler
向客户端发送 HTML5 文件;- http 数据在传输过程中是分段,当浏览器发送大量数据时,就会发出多次 http 请求,
HttpObjectAggregator
可以将 HTTP 消息的多个部分合成一条完整的 HTTP 消息; WebSocketServerProtocolHandler
定义了 WebSocket 的对外暴露地址。
public class MyWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{ private SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { //1.获取Channel通道 final Channel channel=ctx.channel(); //2.创建一个定时线程池 ScheduledExecutorService ses=Executors.newScheduledThreadPool(1); //3.一秒钟之后只需,并且每隔5秒往浏览器发送数据 ses.scheduleWithFixedDelay(new Runnable() { public void run() { String sendTime=format.format(new Date()); channel.writeAndFlush(new TextWebSocketFrame("推送时间=" + sendTime)); } },1,5, TimeUnit.SECONDS); } //接受浏览器消息 @Override protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { System.out.println("收到消息 " + msg.text()); } //当web客户端连接后,触发方法 @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { } //当web客户端断开后,触发方法 @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { } }
代码块预览 复制
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
代码说明:
其实 WebSocket 对于的 Handler 跟我们普通业务的 Handler 没有什么区别,这里主要使用定时线程池定时往浏览器推送消息,这个是传统的 Http+Ajax 请求无法实现的逆向推送效果。
function WebSocketTest() { if ("WebSocket" in window) { var ws = new WebSocket("ws://localhost:7000/hello2"); //发送数据 ws.onopen = function() { ws.send("发送数据"); }; //接受数据 ws.onmessage = function(evt) { var received_msg = evt.data; console.log(">>>"+received_msg) }; //监听连接关闭 ws.onclose = function() { alert("连接已关闭..."); }; } else { alert("您的浏览器不支持 WebSocket!"); } }
代码块预览 复制
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
浏览器控制台每隔 5s 钟就能收到服务端推送过来的消息,如下图所示:
- 我们需要了解 WebSocket 的应用方向,主要是前端和服务端建立长连接通信;
- 掌握 WebSocket 几个事件监听方法,open ()、message ()、error ()、close ();
- 掌握 Netty 编写的服务端写法,主要是几个类 HttpServerCodec、ChunkedWriteHandler、HttpObjectAggregator、WebSocketServerProtocolHandler,Handler 业务跟普通业务没啥区别。