一、实例要求
1)、Http协议是无状态的,浏览器和服务器间的请求响应一次,下一次会重新创建连接
2)、要求:实现基于Websocket的长连接的全双工的交互
3)、改变Http协议多次请求的约束,实现长连接,服务器可以发送消息给浏览器
4)、客户端浏览器和服务器端会相互感知,比如服务器关闭了,浏览器会感知,同样浏览器关闭了,服务器会感知。

二、代码演示
 

/**
 * 实现基于websocket的长连接的全双工交互
 * 改变http协议多次请求的约束,实现长连接,服务器可以发送消息给浏览器
 * 客户端浏览器和服务端会相互感知,比如服务器断了浏览器会感知,浏览器断了服务器会感知
 * @author xinjiao.yu
 * @date 2023/01/30 14:04
 **/
public class WebSocketTest {
    public static void main(String[] args) throws Exception {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .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就是将多个段聚合
                             * 这就是为什么,当浏览器发送大量请求时,会发出多次http请求
                             */
                            pipeline.addLast(new HttpObjectAggregator(8192));
                            /**
                             * 对于websocket他的数据是以帧frame的形式传递的
                             * 可以看到websocketFrame下面有六个子类
                             * 浏览器请求时,ws://localhost:7000/hello表示请求的uri
                             * webSocketServerProtocolHandler核心功能是将http协议升级未ws,保持长连接
                             * 是通过一个状态码101
                             */
                            pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
                            pipeline.addLast(new MyTextWebsocketFrameHandler());
                        }
                    });
            ChannelFuture future = bootstrap.bind(7000).sync();
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}
class MyTextWebsocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        System.out.println("服务器收到消息:"+msg.text());
        TextWebSocketFrame textWebSocketFrame = new TextWebSocketFrame("服务器时间:"+ LocalDateTime.now()+" "+msg.text());
        ctx.channel().writeAndFlush(textWebSocketFrame);
    }
    // 当web客户端连接后,触发方法
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //id表示唯一的值,longtext是唯一的,shortText不是唯一的
        System.out.println("handlerAdded 被调用"+ctx.channel().id().asLongText());
        System.out.println("handlerAdded 被调用"+ctx.channel().id().asShortText());
    }
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved 被调用"+ctx.channel().id().asLongText());
    }
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        System.out.println("异常发生"+cause.getMessage());
        ctx.close();
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        var socket;
        // 判断当前浏览器是否支持webSocket
        if(window.WebSocket){
            // go on
            socket = new WebSocket("ws://localhost:7000/hello");

            // 相当于channelRead0,ev 可以收到服务器端会送的消息
            socket.onmessage = function (ev) {
                var rt = document.getElementById("responseText");
                rt.value = rt.value + "\n" + ev.data;
            }

            // 相当于连接开启(感知到连接开启)
            socket.onopen = function (ev) {
                var rt = document.getElementById("responseText");
                rt.value = "连接开启了...";
            }

            // 相当于连接关闭(感知到连接关闭)
            socket.onclose = function (ev) {
                var rt = document.getElementById("responseText");
                rt.value = rt.value + "\n连接关闭了";
            }
        }else {
            alert("当前浏览器不支持websocket");
        }

        // 发送消息到服务器
        function send(message) {
            if(!window.socket){ // 先判断socket是否创建好
                return;
            }
            if(socket.readyState == WebSocket.OPEN){
                // 通过socket发送消息
                socket.send(message);
            }else{
                alert("连接未开启");
            }
        }
    </script>
    <form onsubmit="return false">
        <textarea name="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>