• Netty笔记(4)


    对HTTP的支持

    服务端代码:

    向 PipeLine中 注册 HttpServerCodec Http协议的编码解码一体的Handler 处理Http请求 封装Http响应

    public class TestServer {
        public static void main(String[] args) throws Exception {
    
            EventLoopGroup bossGroup = new NioEventLoopGroup(1);
            EventLoopGroup workerGroup = new NioEventLoopGroup();
    
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
    
                serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>{
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
    
                            //向管道加入处理器
                            //得到管道
                            ChannelPipeline pipeline = ch.pipeline();
    
                            //加入一个netty 提供的httpServerCodec codec =>[coder - decoder]
                            //HttpServerCodec 说明
                            //1. HttpServerCodec 是netty 提供的处理http的 编-解码器
                            pipeline.addLast("MyHttpServerCodec",new HttpServerCodec());
                            //2. 增加一个自定义的handler
                            pipeline.addLast("MyTestHttpServerHandler", new TestHttpServerHandler());
                            System.out.println("ok~~~~");
    
                        }
                    });
    
                ChannelFuture channelFuture = serverBootstrap.bind(6668).sync();
                
                channelFuture.channel().closeFuture().sync();
    
            }finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    }
    

    自定义Handler:

    过滤 浏览器请求 favicon.ico 的请求 并回送信息

    public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    
    
        //channelRead0 读取客户端数据
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
            System.out.println("对应的channel=" + ctx.channel() + " pipeline=" + ctx
            .pipeline() + " 通过pipeline获取channel" + ctx.pipeline().channel());
    
            System.out.println("当前ctx的handler=" + ctx.handler());
    
            //判断 msg 是不是 httprequest请求
            if(msg instanceof HttpRequest) {
    
                System.out.println("msg 类型=" + msg.getClass());
                System.out.println("客户端地址" + ctx.channel().remoteAddress());
    
                //获取到
                HttpRequest httpRequest = (HttpRequest) msg;
                //获取uri, 过滤指定的资源
                URI uri = new URI(httpRequest.uri());
                if("/favicon.ico".equals(uri.getPath())) {
                    System.out.println("请求了 favicon.ico, 不做响应");
                    return;
                }
                //回复信息给浏览器 [http协议]
    
                ByteBuf content = Unpooled.copiedBuffer("hello, 我是服务器", CharsetUtil.UTF_8);
    
                //构造一个http的相应,即 httpresponse
                FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
    
                response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
                response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
    
                //将构建好 response返回
                ctx.writeAndFlush(response);
    
            }
        }
    }
    

    浏览器 地址栏输入 http://127.0.0.1:6668 连接服务端 并收到服务端信息

    对WebSocket 的支持

    服务端代码:

    添加 将Http协议升级为 webSocket协议的拦截器 WebSocketServerProtocolHandler 并指定路径

    public class MyServer {
        public static void main(String[] args) throws Exception{
    
    
            //创建两个线程组
            EventLoopGroup bossGroup = new NioEventLoopGroup(1);
            EventLoopGroup workerGroup = new NioEventLoopGroup(); //8个NioEventLoop
            try {
    
                ServerBootstrap serverBootstrap = new ServerBootstrap();
    
                serverBootstrap.group(bossGroup, workerGroup);
                serverBootstrap.channel(NioServerSocketChannel.class);
                serverBootstrap.handler(new LoggingHandler(LogLevel.INFO));
                serverBootstrap.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());
    
                        /*
                        说明
                        1. http数据在传输过程中是分段, HttpObjectAggregator ,就是可以将多个段聚合
                        2. 这就就是为什么,当浏览器发送大量数据时,就会发出多次http请求
                         */
                        pipeline.addLast(new HttpObjectAggregator(8192));
                        /*
                        说明
                        1. 对应websocket ,它的数据是以 帧(frame) 形式传递
                        2. 可以看到WebSocketFrame 下面有六个子类
                        3. 浏览器请求时 ws://localhost:7000/hello 表示请求的uri
                        4. WebSocketServerProtocolHandler 核心功能是将 http协议升级为 ws协议 , 保持长连接
                         */
                        pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
    
                        //自定义的handler ,处理业务逻辑
                        pipeline.addLast(new MyTextWebSocketFrameHandler());
                    }
                });
    
                //启动服务器
                ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
                channelFuture.channel().closeFuture().sync();
    
            }finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    }
    

    服务端Handler:

    websocket 协议中传输数据为数据帧 (TextWebSocketFrame)

    //这里 TextWebSocketFrame 类型,表示一个文本帧(frame)
    public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
    
            System.out.println("服务器收到消息 " + msg.text());
            //回复消息
            ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间" + LocalDateTime.now() + " " + msg.text()));
        }
    
        //当web客户端连接后, 触发方法
        @Override
        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());
        }
    
        @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) {
            socket = new WebSocket("ws://localhost:7000/hello");
            //相当于channelReado, ev 收到服务器端回送的消息
            socket.onmessage = function (ev) {
                var rt = document.getElementById("responseText");
                rt.value = rt.value + "
    " + 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 + "
    " + "连接关闭了.."
            }
        } 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;  300px"></textarea>
            <input type="button" value="发生消息" onclick="send(this.form.message.value)">
            <textarea id="responseText" style="height: 300px;  300px"></textarea>
            <input type="button" value="清空内容" onclick="document.getElementById('responseText').value=''">
        </form>
    </body>
    </html>
    

    Netty 的心跳检测机制

    向pipeLine中加入心跳检测的Handler ,监听读空闲 写空闲 读写空闲,并设置时间.,如果在设定时间内没有发生 读写事件, 则会产生一个相关事件,并传递到下一个 Handler 中 (自定义处理Handler)

    服务端代码:

    心跳检测Handler 在监听到相应的事件后 会交由注册的下一个Handler的userEventTriggered方法处理 ,这里注册一个自定义Handler

    public class MyServer {
        public static void main(String[] args) throws Exception{
    
    
            //创建两个线程组
            EventLoopGroup bossGroup = new NioEventLoopGroup(1);
            EventLoopGroup workerGroup = new NioEventLoopGroup(); //8个NioEventLoop
            try {
    
                ServerBootstrap serverBootstrap = new ServerBootstrap();
    
                serverBootstrap.group(bossGroup, workerGroup);
                serverBootstrap.channel(NioServerSocketChannel.class);
                serverBootstrap.handler(new LoggingHandler(LogLevel.INFO));
                serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        //加入一个netty 提供 IdleStateHandler
                        /*
                        说明
                        1. IdleStateHandler 是netty 提供的处理空闲状态的处理器
                        2. long readerIdleTime : 表示多长时间没有读, 就会发送一个心跳检测包检测是否连接
                        3. long writerIdleTime : 表示多长时间没有写, 就会发送一个心跳检测包检测是否连接
                        4. long allIdleTime : 表示多长时间没有读写, 就会发送一个心跳检测包检测是否连接
    
                       
     *                  5. 当 IdleStateEvent 触发后 , 就会传递给管道 的下一个handler去处理
     *                  通过调用(触发)下一个handler 的 userEventTiggered , 在该方法中去处理 IdleStateEvent(读空闲,写空闲,读写空闲)
                         */
                        pipeline.addLast(new IdleStateHandler(7000,7000,10, TimeUnit.SECONDS));
                        //加入一个对空闲检测进一步处理的handler(自定义)
                        pipeline.addLast(new MyServerHandler());
                    }
                });
    
                //启动服务器
                ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
                channelFuture.channel().closeFuture().sync();
    
            }finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    }
    

    处理事件的Handler (userEventTriggered方法中处理) :

    public class MyServerHandler extends ChannelInboundHandlerAdapter {
    
        /**
         *
         * @param ctx 上下文
         * @param evt 事件
         * @throws Exception
         */
        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    
            if(evt instanceof IdleStateEvent) {
                //将  evt 向下转型 IdleStateEvent
                IdleStateEvent event = (IdleStateEvent) evt;
                String eventType = null;
                switch (event.state()) {
                    case READER_IDLE:
                      eventType = "读空闲";
                      break;
                    case WRITER_IDLE:
                        eventType = "写空闲";
                        break;
                    case ALL_IDLE:
                        eventType = "读写空闲";
                        break;
                }
                System.out.println(ctx.channel().remoteAddress() + "--超时时间--" + eventType);
                System.out.println("服务器做相应处理..");
                //如果发生空闲,我们关闭通道
               // ctx.channel().close();
            }
        }
    }
    
  • 相关阅读:
    20145221 《信息安全系统设计基础》实验四 外设驱动程序设计
    20145221 《信息安全系统设计基础》第11周学习总结
    第09组 Alpha冲刺(4/6)
    第09组 Alpha冲刺(3/6)
    第09组 Alpha冲刺(2/6)
    第09组 Alpha冲刺(1/6)
    第09组 团队Git现场编程实战
    第09组 团队项目-需求分析报告
    团队项目-选题报告
    第二次结对编程作业
  • 原文地址:https://www.cnblogs.com/xjwhaha/p/13554870.html
Copyright © 2020-2023  润新知