Netty入门(七)WebSocket长连接开发

Http是无状态的,浏览器每次请求,都是创建一个新连接,传输完毕即断开。双方并不能感知对方的状态。
WebSocket是长连接,一次TCP握手,即可建立持久性的连接,并且双方能感知到对方的状态。

实例要求:

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

核心代码:

//创建一个通道测试对象(匿名对象)
new ChannelInitializer<SocketChannel>() {
    //给管道设置处理器
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        //因为是基于http协议,使用http的编码和解码器
        pipeline.addLast(new HttpServerCodec());
        //http是以块方式写的,添加ChunkedWrite处理器
        pipeline.addLast(new ChunkedWriteHandler());
        //因为http的数据,在传输过程中是分段的,HttpObjectAggregator就是可以将多个段聚合起来
        //这就是为什么,当浏览器发送大量数据时,就会发出多次http请求的原因
        pipeline.addLast(new HttpObjectAggregator(8192));
        //对于webSocket的数据是以帧的形式传递
        //可以看到webSocketFrame,下面有六个子类
        //浏览器请求时 ws://localhost:7000/hello 表示请求的url,使用的是ws webSocket协议
        //WebSocketServerProtocolHandler核心功能将http协议升级为ws协议,保持长连接
        pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
        //自定义的handler,处理业务逻辑
        pipeline.addLast(new MyTextWebSocketFrameHandler());
    }
}
  • HttpServerCodec :Http编码和解码器
  • ChunkedWriteHandler :以块的方式写数据
  • HttpObjectAggregator(8192) :将多个段聚合起来,最大长度8192
  • WebSocketServerProtocolHandler:将http协议升级为WebSocket协议,通过状态码101切换的。101(switch protocols)。构造方法参数是ws的路径
  • WebSocket传输数据以frame(帧)的形式传递:

完整代码

服务端:

public class MyServer {
    public static void main(String[] args) {
        //设置main方法日志级别
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        List<Logger> loggerList = loggerContext.getLoggerList();
        loggerList.forEach(logger -> {
            logger.setLevel(Level.WARN);
        });

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(3);

        try {
            //2.创建服务器端的启动对象,配置参数
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //3.使用链式编程进行设置
            serverBootstrap
                    //设置两个线程组
                    .group(bossGroup, workerGroup)
                    //使用NioServerSocketChannel作为服务器的通道实现
                    .channel(NioServerSocketChannel.class)
                    //改handler对应的是bossGroup
                    .handler(new LoggingHandler(LogLevel.INFO))
                    //给我们的workerGroup的EventLoopGroup对应的管道设置处理器Handler
                    .childHandler(
                            //创建一个通道测试对象(匿名对象)
                            new ChannelInitializer<SocketChannel>() {
                                //给管道设置处理器
                                @Override
                                protected void initChannel(SocketChannel socketChannel) throws Exception {
                                    ChannelPipeline pipeline = socketChannel.pipeline();
                                    //因为是基于http协议,使用http的编码和解码器
                                    pipeline.addLast(new HttpServerCodec());
                                    //http是以块方式写的,添加ChunkedWrite处理器
                                    pipeline.addLast(new ChunkedWriteHandler());
                                    //因为http的数据,在传输过程中是分段的,HttpObjectAggregator就是可以将多个段聚合起来
                                    //这就是为什么,当浏览器发送大量数据时,就会发出多次http请求的原因
                                    pipeline.addLast(new HttpObjectAggregator(8192));
                                    //对于webSocket的数据是以帧的形式传递
                                    //可以看到webSocketFrame,下面有六个子类
                                    //浏览器请求时 ws://localhost:7000/hello 表示请求的url,使用的是ws webSocket协议
                                    //WebSocketServerProtocolHandler核心功能将http协议升级为ws协议,保持长连接
                                    pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
                                    //自定义的handler,处理业务逻辑
                                    pipeline.addLast(new MyTextWebSocketFrameHandler());
                                }
                            });
            //启动服务器
            ChannelFuture sync = serverBootstrap.bind(8005).sync();
            sync.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

自定义的handler:

//泛型TextWebSocketFrame类型,表示一个文本帧(frame)
public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
        System.out.println("服务器端收到消息:"+textWebSocketFrame.text());
        //回复浏览器
        channelHandlerContext.channel().writeAndFlush(new TextWebSocketFrame("服务器时间 "+ LocalDateTime.now()+" "+textWebSocketFrame.text()));
    }
    //当web客户端连接后,触发方法
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //id 表示唯一的值,LongText是唯一的 shortText不是唯一
        System.out.println("handlerAdded被调用 "+ctx.channel().id().asLongText());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved被调用 "+ctx.channel().id().asLongText());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常发生 "+cause.getMessage());
        ctx.close();//关闭连接
    }
}

html页面:注意这里的端口和访问路径必须和服务端相同

<!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:8005/hello");
        //相当于channelReado, 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>

测试:启动服务端和html
在这里插入图片描述
在这里插入图片描述
关闭服务端:
在这里插入图片描述
发送内容:
在这里插入图片描述

为什么http协议能升级到ws协议呢

WebSocketServerProtocolHandler通过一个状态码101
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值