9.基于netty实现WebSocket服务器

【README】

1.本文总结自B站《netty-尚硅谷》,很不错;

2.本文示例代码基于netty实现 WebSocket服务器功能;

  • 其中, html作为WebSocket客户端;

3.WebSocket协议介绍:

  •  对于 WebSocket,它的数据是以帧 frame 的形式传递的
  •  可以看到 WebSocketFrame 下面有6个子类
  •  浏览器发送请求时: ws://localhost:8089/hello 表示请求的uri
  •  WebSocketServerProtocolHandler 核心功能是把 http协议升级为 ws 协议,保持长连接; 是通过一个状态码 101 来切换的;

4.本文末尾po出了 WebSocket  请求响应报文截图;


【1】基于Netty 实现WebSocket服务器和客户端长连接:

1)WebSocket概述:

  • 同http类似,WebSocket也是一种应用层传输协议,基于WebSocket,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

2)WebSocket协议特点(转自阮一峰 WebSocket 教程 - 阮一峰的网络日志):

  1. 建立在 TCP 协议之上,服务器端的实现比较容易。
  2. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
  3. 数据格式比较轻量,性能开销小,通信高效。
  4. 可以发送文本,也可以发送二进制数据。
  5. 没有同源限制,客户端可以与任意服务器通信
  6. 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

 

3)需求描述:

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

【1.1】WebSocket服务器

/**
 * @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();
    }
}

【1.2】WebSocket客户端-html(浏览器)

hello.html:




    
    WebSocket客户端
    


    

 


 【1.3】演示效果:

1)后端运行日志

handlerAdded 被调用, channel.id.longText = 005056fffec00001-00006848-0000000b-0f972ff18d1b6997-7d33882d
handlerAdded 被调用, channel.id.shortText = 7d33882d
服务器端收到消息 = hello 世界

2)前端交互页面

9.基于netty实现WebSocket服务器_第1张图片

 3)浏览器关闭时,后端反应:(客户端关闭,WebSocket服务器可以识别到)

handlerRemoved 被调用, channel.id.longText = 005056fffec00001-00006848-0000000b-0f972ff18d1b6997-7d33882d

4)服务器关闭后,前端反应:(服务器关闭,WebSocket客户端可以识别到)

9.基于netty实现WebSocket服务器_第2张图片

 


【1.4】WebSocket请求响应报文

1.请求html

9.基于netty实现WebSocket服务器_第3张图片

 2.请求WebSocket服务器
9.基于netty实现WebSocket服务器_第4张图片

 

你可能感兴趣的:(netty,websocket,网络协议,网络)