zoukankan      html  css  js  c++  java
  • 009-核心技术-netty-通过WebSocket编程实现服务器和客户端长连接

    一、长连接

    http协议是无状态的,浏览器和服务器间的请求相应一次,下一次会重新创建连接

    1、服务端代码

    package com.github.bjlhx15.netty.demo.netty.websocket;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelOption;
    import io.netty.channel.ChannelPipeline;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.handler.codec.http.HttpObjectAggregator;
    import io.netty.handler.codec.http.HttpServerCodec;
    import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
    import io.netty.handler.logging.LogLevel;
    import io.netty.handler.logging.LoggingHandler;
    import io.netty.handler.stream.ChunkedWriteHandler;
    
    public class MyServer {
        public static void main(String[] args) throws InterruptedException {
            NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
            NioEventLoopGroup workerGroup = new NioEventLoopGroup();
    
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.group(bossGroup, workerGroup)
                        .channel(NioServerSocketChannel.class)
                        .handler(new LoggingHandler(LogLevel.INFO))//在bossGroup增加一个日志处理器
                        .option(ChannelOption.SO_BACKLOG, 128)
                        .childOption(ChannelOption.SO_KEEPALIVE, true)
                        .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 是将多个端聚合
                                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 channelFuture = serverBootstrap.bind(7000).sync();
    
                System.out.println("Server start listen at " + 7000);
                channelFuture.channel().closeFuture().sync();
            } finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    }

    服务端handler

    package com.github.bjlhx15.netty.demo.netty.websocket;
    
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
    
    import java.time.LocalDateTime;
    
    //TextWebSocketFrame表示一个文本帧
    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()));
        }
    
        @Override
        public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
            //id 表示唯一至,asLongText是唯一的,asShortText不是唯一的
            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("handlerRemoved 被调用:" + cause.getMessage());
            ctx.close();
        }
    }

    客户端页面

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <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>
    <script>
        var socket;
        if (window.WebSocket) {
            socket = new WebSocket("ws://localhost:7000/hello");
            socket.onmessage = function (ev) {
                let rt = document.getElementById("responseText");
                rt.value = rt.value + "
    " + ev.data;
            }
            socket.onopen = function (ev) {
                let rt = document.getElementById("responseText");
                rt.value = "连接开启";
            }
            socket.onclose = function (ev) {
                let rt = document.getElementById("responseText");
                rt.value = rt.value + "
    " + "连接关闭";
            }
        } else {
            alert("浏览器不支持");
        }
    
        function send(msg) {
            if (!window.socket) {
                return;
            }
            if (socket.readyState == WebSocket.OPEN) {
                socket.send(msg);
            } else {
                alert("连接没有开启")
            }
    
        }
    </script>
    </html>

    使用idea run下main以及html即可调试。

    转载请注明出处,感谢。
    作者:李宏旭
    阅罢此文,如果您觉得本文不错并有所收获,请【打赏】或【推荐】,也可【评论】留下您的问题或建议与我交流。
    你的支持是我不断创作和分享的不竭动力!
  • 相关阅读:
    Https的请求过程
    计算机网络知识
    数据结构之图
    Python3线程池进程池
    数据结构之堆heapq
    EffectivePython并发及并行
    EffectivePython类与继承
    EffectivePython并发及并行
    5.19完全数
    5.18数字全排列
  • 原文地址:https://www.cnblogs.com/bjlhx/p/15121244.html
Copyright © 2011-2022 走看看