Netty WebSocket 协议
1. 前言
上一节,我们主要讲解了 Http 协议,本节我们来讲解 Netty 支持的另外一个 WebSocket 协议,WebSocket 是 HTML5 开始支持的全双工协议。
在真实的开发当中,其实 WebSocket 使用非常的广泛,特别是在目前前端技术越来越完善的情况下,很多应用都是基于前端技术去实现,比如:APP、小程序等主流应用,前端的技术完全可以开发出类似原生技术一样的产品,并且开发效率上比原生更加的快、用户体验更好。
这些应用涉及通信、聊天、推送等业务,则可以使用 WebSocket 来实现,因此,WebSocket 已经是目前非常主流的浏览器和服务端建立长连接的通信技术。
WebSocket 协议架构图如下所示:
2. 学习目的
Netty 虽然可以开发客户端和服务端的应用,但是 Netty 和 WebSocket 结合则是另外一个不同的应用方向。
应用方向一: 纯后端的应用,比如:框架、中间件通信等,则完全可以使用 Netty 来开发客户端和服务端,并且双方通过 TCP 协议来进行通信。
应用方向二: 前端(浏览器)和服务端之间通信,并且想实现类似长连接的效果,那么 WebSocket 和 Netty 则是主流,并且这部分的应用场景非常的广泛和运用非常的多。
因此,我们在学习完本章内容之后,我们就可以掌握这类的需求的开发(比如:聊天、消息推送),自己可以尝试去开发一款小程序在线聊天系统。
当真实的开发当中,也许很多人直接使用 Spring 封装的 WebSocket 或者其它第三方的框架(比如:netty-socketio)去实现。本节我们主要讲解原生 Netty 来开发 WebSocket 协议格式的数据请求,毕竟会用和知道知其所以然还是有区别的。
3. WebSocket 的 API
创建 WebSocket 对象,第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。
var Socket = new WebSocket(url, [protocol] );
WebSocket 事件
事件 | 事件处理程序 | 描述 |
---|---|---|
open | Socket.onopen | 连接建立时触发 |
message | Socket.onmessage | 客户端接收服务端数据时触发 |
error | Socket.onerror | 通信发生错误时触发 |
close | Socket.onclose | 连接关闭时触发 |
4. 环境搭建
本节,我们主要开发一个 Demo,主要功能如下:
- 前端页面(html)启动的时候,连接 WebSocket 服务端;
- 服务端往前端每隔 5s 推送一条消息。
环境搭建步骤:
- 创建一个 Maven 项目;
- 导入 Netty 坐标。
实例:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
5. 代码实现
5.1. Netty 主启动类
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();
}
}
}
代码说明:
- WebSocket 比 TCP 和 Http 协议都稍微复杂有点,它其实是 TCP 和 Http 协议的结合,首先是连接之前发送的是 Http 协议请求,但是新增 Http 所没有的附加头信息
Upgrade: WebSocket
表明是一个申请协议升级的 Http 请求。其次,真正建立连接之后,其实底层是 TCP 协议长连接; HttpServerCodec
将请求和应答消息解码为 HTTP 消息;ChunkedWriteHandler
向客户端发送 HTML5 文件;- http 数据在传输过程中是分段,当浏览器发送大量数据时,就会发出多次 http 请求,
HttpObjectAggregator
可以将 HTTP 消息的多个部分合成一条完整的 HTTP 消息; WebSocketServerProtocolHandler
定义了 WebSocket 的对外暴露地址。
5.2 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 {
}
}
代码说明:
其实 WebSocket 对于的 Handler 跟我们普通业务的 Handler 没有什么区别,这里主要使用定时线程池定时往浏览器推送消息,这个是传统的 Http+Ajax 请求无法实现的逆向推送效果。
5.3. 前端代码
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!");
}
}
5.4 测试效果
浏览器控制台每隔 5s 钟就能收到服务端推送过来的消息,如下图所示:
6. 小结
- 我们需要了解 WebSocket 的应用方向,主要是前端和服务端建立长连接通信;
- 掌握 WebSocket 几个事件监听方法,open ()、message ()、error ()、close ();
- 掌握 Netty 编写的服务端写法,主要是几个类 HttpServerCodec、ChunkedWriteHandler、HttpObjectAggregator、WebSocketServerProtocolHandler,Handler 业务跟普通业务没啥区别。