• Netty实现WebSocket通信


    /*
    * Netty Reactor模型 -
    * 1.单线程模型:一个用户一个线程来处理,线程有极限
    * 2.多线程模型:加入线程池,线程池线程轮询执行任务
    * 3.主从多线程模型:俩个线程池,一个线程池接收请求,一个线程池处理IO(推荐,适用高并发环境)
    *
    * 以下代码为主从多线程模型
    * */

    映入坐标:
    <dependencies>
    <dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.25.Final</version>
    </dependency>
    </dependencies>


    服务器端:
    package cn.web.netty;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    
    /*
    * Netty Reactor模型 -
    * 1.单线程模型:一个用户一个线程来处理,线程有极限
    * 2.多线程模型:加入线程池,线程池线程轮询执行任务
    * 3.主从多线程模型:俩个线程池,一个线程池接收请求,一个线程池处理IO(推荐,适用高并发环境)
    *
    * 以下代码为主从多线程模型
    * */
    public class WebSocketServer {
        public static void main(String[] args) {
            NioEventLoopGroup mainGrp=new NioEventLoopGroup();//主线程池
            NioEventLoopGroup subGrp=new NioEventLoopGroup();//从线程池
    
            try {
                //1.创建netty服务器启动对象
                ServerBootstrap serverBootstrap=new ServerBootstrap();
    
                //2.初始化
                serverBootstrap
                        //指定使用的线程池
                        .group(mainGrp,subGrp)
                        //指定netty通道类型
                        .channel(NioServerSocketChannel.class)
                        // 指定通道初始化器用来加载当Channel收到事件消息后,
                        // 如何进行业务处理
                        .childHandler(new WebSocketChannelInitializer());
    
                //3.绑定端口,以同步的方式启动
                ChannelFuture future=serverBootstrap.bind(9090).sync();
    
                //4.等待服务关闭
                future.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
    
                //5.异常关闭服务器
                mainGrp.shutdownGracefully();
                subGrp.shutdownGracefully();
            }
    
        }
    }
    
    
    package cn.web.netty;

    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelPipeline;
    import io.netty.channel.socket.SocketChannel;
    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.stream.ChunkedWriteHandler;
    import io.netty.handler.timeout.IdleStateHandler;


    /**
    * 通道初始化器
    * 用来加载通道处理器(ChannelHandler)
    */
    public class WebSocketChannelInitializer extends ChannelInitializer<SocketChannel> {

    // 初始化通道
    // 在这个方法中去加载对应的ChannelHandler
    protected void initChannel(SocketChannel socketChannel) throws Exception {

    // 获取管道,将一个一个的ChannelHandler添加到管道中
    ChannelPipeline pipeline = socketChannel.pipeline();

    // 添加一个http的编解码器
    pipeline.addLast(new HttpServerCodec());
    // 添加一个用于支持大数据流的支持
    pipeline.addLast(new ChunkedWriteHandler());
    // 添加一个聚合器,这个聚合器主要是将HttpMessage聚合成FullHttpRequest/Response
    pipeline.addLast(new HttpObjectAggregator(1024*64));
    // 需要指定接收请求的路由
    // 必须使用以ws后缀结尾的url才能访问
    pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
    // 添加自定义的Handler
    pipeline.addLast(new ChatHandler());

    // 增加心跳事件支持
    // 第一个参数: 读空闲4秒
    // 第二个参数: 写空闲8秒
    // 第三个参数: 读写空闲12秒
    pipeline.addLast(new IdleStateHandler(4,8,12));
    pipeline.addLast(new HearBeatHandler());
    }
    }
     
    package cn.web.netty;
    
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.channel.group.ChannelGroup;
    import io.netty.channel.group.DefaultChannelGroup;
    import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
    import io.netty.util.concurrent.GlobalEventExecutor;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    
    public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    
        //存储用户连接
        private static ChannelGroup clients=new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
        private SimpleDateFormat sdf=new SimpleDateFormat("yyyy-mm-dd hh:MM:ss");
    
        // 当Channel中有新的事件消息会自动调用
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
            String text=textWebSocketFrame.text();
            System.out.println("接收到消息:"+text);
    
            // 获取客户端发送过来的文本消息
            for (Channel client :clients) {
                // 将消息发送到所有的客户端
                client.writeAndFlush(new TextWebSocketFrame(sdf.format(new Date())+":"+text));
            }
        }
    
        // 当有新的客户端连接服务器之后,会自动调用这个方法
        @Override
        public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
            clients.add(ctx.channel());
        }
    
        // 端口连接处理
        @Override
        public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
            System.out.println("断开连接");
            clients.remove(ctx.channel());
        }
    }
    package cn.web.netty;
    
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.handler.timeout.IdleState;
    import io.netty.handler.timeout.IdleStateEvent;
    
    public class HearBeatHandler extends ChannelInboundHandlerAdapter {
        // 客户端在一定的时间没有动作就会触发这个事件
        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
            if (evt instanceof IdleStateEvent){
                IdleStateEvent event=(IdleStateEvent)evt;
    
                if (event.state()== IdleState.READER_IDLE){
                    System.out.println("读空闲");
                }else if(event.state()==IdleState.WRITER_IDLE){
                    System.out.println("写空闲");
                }else if(event.state()==IdleState.ALL_IDLE){
                    System.out.println("读写都空闲你,关闭通道");
                    ctx.channel().close();
                }
            }
        }
    }

    HTML页面连接:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>在线聊天室</title>
    </head>
    <body>
        <input type="text" id="message">
        <input type="button" value="发送消息" onclick="sendMsg()">
    
        接收到的消息:
        <p id="server_message" style="background-color: #AAAAAA"></p>
    
        <script>
    
            var websocket = null;
    
            // 判断当前浏览器是否支持websocket
            if(window.WebSocket) {
                websocket = new WebSocket("ws://127.0.0.1:9090/ws");
    
                websocket.onopen = function() {
                    console.log("建立连接.");
                }
                websocket.onclose = function() {
                    console.log("断开连接");
                }
                websocket.onmessage = function(e) {
                    console.log("接收到服务器消息:" + e.data);
                    var server_message = document.getElementById("server_message");
                    server_message.innerHTML += e.data + "<br/>";
                }
            }
            else {
                alert("当前浏览器不支持web socket");
            }
    
            function sendMsg() {
                var message = document.getElementById("message");
                websocket.send(message.value);
            }
        </script>
    </body>
    </html>
  • 相关阅读:
    jqueryautocomplete
    了解CSS的查找匹配原理 让CSS更简洁、高效
    html5网页编码
    刚开始学习 mvc碰到的郁闷问题
    datatable 批量插入方法 求解?
    28个经过重新设计的著名博客案例(1120)
    递归调用中的return
    C++新建一个模板
    C++ 中用 sizeof 判断数组长度
    为什么MySQL选择REPEATABLE READ作为默认隔离级别?
  • 原文地址:https://www.cnblogs.com/zhuyapeng/p/13920511.html
Copyright © 2020-2023  润新知