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<SocketChannel>() {
                        @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<TextWebSocketFrame> {

    // 读取客户端发送的请求报文
    @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:

<!DOCTYPE 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.3】演示效果:

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.4】WebSocket请求响应报文

1.请求html

 2.请求WebSocket服务器

 

  • 1
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值