1.本文总结自B站《netty-尚硅谷》,很不错;
2.本文示例代码基于netty实现 WebSocket服务器功能;
3.WebSocket协议介绍:
4.本文末尾po出了 WebSocket 请求响应报文截图;
1)WebSocket概述:
2)WebSocket协议特点(转自阮一峰 WebSocket 教程 - 阮一峰的网络日志):
3)需求描述:
- /**
- * @Description 基于netty的webSocket服务器
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年09月04日
- */
- public class NettyWebSocketServer68 {
- public static void main(String[] args) {
- try {
- new NettyWebSocketServer68().run();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- public void run() throws InterruptedException {
- // 创建线程池执行器
- EventLoopGroup bossGroup = new NioEventLoopGroup(1);
- EventLoopGroup workerGroup = new NioEventLoopGroup(8);
- try {
- // 服务器启动引导对象
- ServerBootstrap serverBootstrap = new ServerBootstrap();
- serverBootstrap.group(bossGroup, workerGroup)
- .channel(NioServerSocketChannel.class)
- .option(ChannelOption.SO_BACKLOG, 128)
- .option(ChannelOption.SO_KEEPALIVE, true)
- .handler(new LoggingHandler(LogLevel.INFO)) // 为 bossGroup 添加 日志处理器
- .childHandler(new ChannelInitializer
() { - @Override
- protected void initChannel(SocketChannel socketChannel) throws Exception {
- ChannelPipeline pipeline = socketChannel.pipeline();
- // 因为使用http协议,所以需要使用http的编码器,解码器
- pipeline.addLast(new HttpServerCodec());
- // 以块方式写,添加 chunkedWriter 处理器
- pipeline.addLast(new ChunkedWriteHandler());
- /**
- * 说明:
- * 1. http数据在传输过程中是分段的,HttpObjectAggregator可以把多个段聚合起来;
- * 2. 这就是为什么当浏览器发送大量数据时,就会发出多次 http请求的原因
- */
- pipeline.addLast(new HttpObjectAggregator(8192));
- /**
- * 说明:
- * 1. 对于 WebSocket,它的数据是以帧frame 的形式传递的;
- * 2. 可以看到 WebSocketFrame 下面有6个子类
- * 3. 浏览器发送请求时: ws://localhost:7000/hello 表示请求的uri
- * 4. WebSocketServerProtocolHandler 核心功能是把 http协议升级为 ws 协议,保持长连接;
- * 是通过一个状态码 101 来切换的
- */
- pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
- // 自定义handler ,处理业务逻辑
- pipeline.addLast(new NettyWebSocketServerHandler());
- }
- });
- // 启动服务器,监听端口,阻塞直到启动成功
- ChannelFuture channelFuture = serverBootstrap.bind(8089).sync();
- // 阻塞直到channel关闭
- channelFuture.channel().closeFuture().sync();
- } finally {
- bossGroup.shutdownGracefully().sync();
- workerGroup.shutdownGracefully().sync();
- }
- }
- }
WebSocket服务器处理器:
- /**
- * @Description 基于netty的WebSocket服务器处理器
- * , TextWebSocketFrame 表示一个文本帧
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年09月04日
- */
- public class NettyWebSocketServerHandler extends SimpleChannelInboundHandler
{ -
- // 读取客户端发送的请求报文
- @Override
- protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
- System.out.println("服务器端收到消息 = " + msg.text());
- // 回复消息
- ctx.channel().writeAndFlush(new TextWebSocketFrame(DateUtils.getNowTimestamp() + "服务器回复:" + msg.text()));
- }
- // 当web客户端连接后,触发该方法
- @Override
- public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
- // ctx.channel().id() 表示唯一的值
- System.out.println("handlerAdded 被调用, channel.id.longText = " + ctx.channel().id().asLongText());
- System.out.println("handlerAdded 被调用, channel.id.shortText = " + ctx.channel().id().asShortText());
- }
- // 客户端离线
- @Override
- public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
- // ctx.channel().id() 表示唯一的值
- System.out.println("handlerRemoved 被调用, channel.id.longText = " + ctx.channel().id().asLongText());
- }
- // 处理异常
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- System.out.println("异常发生,异常消息 = " + cause.getMessage());
- // 关闭连接
- // ctx.close();
- ctx.channel().close();
- }
- }
hello.html:
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>WebSocket客户端title>
- <script>
- var socket;
- // 判断当前浏览器是否支持 WebSocket 编程
- if (!window.WebSocket) {
- alert("当前浏览器不支持websocket");
- } else {
- socket = new WebSocket("ws://localhost:8089/hello");
- // 把服务器回复的消息回显
- socket.onmessage = function(eval) {
- var widget = document.getElementById("responseText");
- widget.value = widget.value + "\n" + eval.data;
- }
- // 连接服务器成功
- socket.onopen = function(eval) {
- console.log(eval);
- var widget = document.getElementById("responseText");
- widget.value = "连接开启了";
- }
- // 连接关闭了
- socket.onclose = function(eval) {
- var widget = document.getElementById("responseText");
- widget.value = eval.value + "\n" + "连接关闭了";
- }
- }
- // 发送消息到服务器
- function send(msg) {
- if(!window.socket) return ;
- if (socket.readyState == WebSocket.OPEN) {
- // 通过socket 发送消息
- socket.send(msg);
- } else {
- alert("连接没有开启");
- }
- }
- script>
- head>
- <body>
- <form onsubmit="return false">
- <textarea id="message" style="height:300px; width:300px">textarea>
- <input type="button" value="发送消息" onclick="send(this.form.message.value)">
- <textarea id="responseText" style="height:300px; width:300px">textarea>
- <input type="button" value="清空内容" onclick="document.getElementById('responseText').value=''">
- form>
- body>
- html>
1)后端运行日志
handlerAdded 被调用, channel.id.longText = 005056fffec00001-00006848-0000000b-0f972ff18d1b6997-7d33882d
handlerAdded 被调用, channel.id.shortText = 7d33882d
服务器端收到消息 = hello 世界
2)前端交互页面

3)浏览器关闭时,后端反应:(客户端关闭,WebSocket服务器可以识别到)
handlerRemoved 被调用, channel.id.longText = 005056fffec00001-00006848-0000000b-0f972ff18d1b6997-7d33882d
4)服务器关闭后,前端反应:(服务器关闭,WebSocket客户端可以识别到)

1.请求html

2.请求WebSocket服务器