Java-第十七部分-NIO和Netty-群聊系统和心跳检测机制

196 阅读3分钟

NIO和Netty全文

群聊系统

服务端

public class GroupChatServer {
    private int port;

    public GroupChatServer(int port) {
        this.port = port;
    }
    //处理客户端请求
    public void run() throws InterruptedException {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap sbs = new ServerBootstrap();
            sbs.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //获取pipeline
                            //加入解码器 编码器
                            ch.pipeline().addLast("decoder", new StringDecoder());
                            ch.pipeline().addLast("encoder", new StringEncoder());
                            //业务处理handler
                            ch.pipeline().addLast("GCServerHandler", new GroupChatServerHandler());
                        }
                    });
            System.out.println("server is ready...");
            ChannelFuture cf = sbs.bind(port).sync();
            cf.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    public static void main(String[] args) {
        try {
            new GroupChatServer(8888).run();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ServerHandler

public class GroupChatServerHandler extends SimpleChannelInboundHandler<String> {
    //定义channel组,管理所有的channel
    //GlobalEventExecutor.INSTANCE全局的事件执行器单例,执行ChannelGroup
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    //连接建立时,一旦连接,就被执行
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //将当前channel加入到channelGroup
        Channel channel = ctx.channel();
        //推送该客户端加入聊天的信息进行推送
        //会将channelGroup遍历,并发送消息
        channelGroup.writeAndFlush(sdf.format(new Date()) + " client " + channel.remoteAddress() + " 加入聊天\r\n");
        channelGroup.add(channel);
    }

    //断开连接触发,进行群发
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        //触发该方法时,自动从channelGroup中移除
        Channel channel = ctx.channel();
        channelGroup.writeAndFlush(sdf.format(new Date()) + " client " + channel.remoteAddress() + " 离开聊天\r\n");
        System.out.println("当前channelGroup大小: " + channelGroup.size());
    }

    //channel处理活动的状态,提示客户端上线,提示服务器
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(sdf.format(new Date()) + " " + ctx.channel().remoteAddress() + " 上线...");
    }

    //channel处于非活动状态,离线
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(sdf.format(new Date()) + " " + ctx.channel().remoteAddress() + " 离线...");
    }

    //读取数据
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        //获取当前的channel
        Channel channel = ctx.channel();
        //遍历channelGroup,根据不同的情况,回送不同的消息
        channelGroup.forEach(ch -> {
            if (channel != ch) {
                //转发消息
                ch.writeAndFlush(sdf.format(new Date()) + " client " + channel.remoteAddress() + " send: " + msg  + "\r\n");
            } else {
                ch.writeAndFlush(sdf.format(new Date()) + " me " + msg + "\r\n");
            }
        });
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //关闭
        cause.printStackTrace();
        ctx.close();
    }
}

客户端

public class GroupChatClient {
    private final String host;
    private final int port;

    public GroupChatClient(String host, int port) {
        this.host = host;
        this.port = port;
    }
    public void run() throws InterruptedException {
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bs = new Bootstrap().group(group).channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast("decoder", new StringDecoder());
                            ch.pipeline().addLast("encoder", new StringEncoder());
                            ch.pipeline().addLast("GCClientHandler", new GroupChatClientHandler());
                        }
                    });
            //等待bootstrap中的异步方法执行完,在启动连接,阻塞在此
            ChannelFuture cf = bs.connect(host, port).sync();
            Channel channel = cf.channel();
            System.out.println("client channel: " + channel.localAddress() + " is ready...");
            //客户端输入信息,创建扫描器
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNextLine()) {
                String msg = scanner.nextLine();
                //发送到服务器端
                channel.writeAndFlush(msg);
            }
            cf.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
    public static void main(String[] args) {
        try {
            new GroupChatClient("localhost", 8888).run();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ClientHandler

public class GroupChatClientHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        //用于删除字符串的头尾空白符
        System.out.println(msg.trim());
    }
}

实现单对单联系

  • 用map管理
//使用HashMap管理
public static Map<String, Channel> channelMap = new HashMap<>();
//账号密码
public static Map<User, Channel> userMap = new HashMap<>();
  • handlerAdded添加
channelMap.put("id" + channel.hashCode(), channel);
userMap.put(new User(channel.hashCode(), "password"), channel);
  • channelRead0实现
if (channelMap.get("id2") != channel) {
    Channel id2 = channelMap.get("id2");
    id2.writeAndFlush(sdf.format(new Date()) + " client " + channel.remoteAddress() + " 私发消息 send: " + msg  + "\r\n");
}

心跳检测机制

  • 从服务器的角度,检查读写情况
  • 心跳检测,防止服务端检测不到客户端是否断开
  • 服务器
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
    new ServerBootstrap().group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
            .handler(new LoggingHandler(LogLevel.INFO)) //日志处理器
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    //加入IdleStateHandler
                    //netty提供的处理空闲状态的处理器
                    //long readerIdleTime, 表示服务端多长时间没有读,发送心跳检测包,检测是否是连接状态
                    // long writerIdleTime, //表示服务端多长时间没有写
                    // long allIdleTime, //表示服务端多长时间没有读写
                    // TimeUnit unit 时间单位
                    // Triggers an IdleStateEvent when a Channel has not performed read, write, or both operation for a while.
                    // 当channel没有读写操作时,触发事件,就会传递给管道的下一个handler处理,通过调用下一个handler的userEventTiggered方法
                    pipeline.addLast(new IdleStateHandler(3, 5, 7, TimeUnit.SECONDS));
                    //加入对空闲检测自定义的handler
                    pipeline.addLast(new MyServerIdleHandler());
                }
            }).bind(8888).sync().channel().closeFuture().sync();
} finally {
     bossGroup.shutdownGracefully();
     workerGroup.shutdownGracefully();
}
  • 处理器
public class MyServerIdleHandler extends ChannelInboundHandlerAdapter {
    /**
     *
     * @param ctx 上下文
     * @param evt 事件
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof 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);
            //发送空闲,关闭通道
            ctx.channel().close();
        }
    }
}