Netty实现连接检测

1.为什么要实现

我们在使用netty的时候,可能tcp连接已近断开了,但是应用层没有检测到,或者一个连接很久没有进行操作了,但是却把服务器的资源占用了,所以以上这些情况,我们就需要进行超时检测。

2.服务端实现

服务端实现主要就是靠一个读写状态事件检测处理器,当服务端检测到一个连接,多少秒或者没有读写操作了就可以去断开这个连接,好让其他更多的连接建立。(一般检测写事件就行了)

@Slf4j
public class ChatServer {
    public static void main(String[] args) {
        NioEventLoopGroup boss = new NioEventLoopGroup();
        NioEventLoopGroup worker = new NioEventLoopGroup();
        LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
        MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
        LoginRequestMessageHandler loginRequestMessageHandler = new LoginRequestMessageHandler();
        ChatRequestMessageHandler chatRequestMessageHandler = new ChatRequestMessageHandler();
        //群聊相关消息的处理
        GroupCreateRequestMessageHandler groupCreateRequestMessageHandler = new GroupCreateRequestMessageHandler();
        GroupChatRequestMessageHandler groupChatRequestMessageHandler = new GroupChatRequestMessageHandler();
        GroupJoinRequestMessageHandler groupJoinRequestMessageHandler = new GroupJoinRequestMessageHandler();
        GroupMembersRequestMessageHandler groupMembersRequestMessageHandler = new GroupMembersRequestMessageHandler();
        GroupQuitRequestMessageHandler groupQuitRequestMessageHandler = new GroupQuitRequestMessageHandler();
        QuitChandler quitChandler = new QuitChandler();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.group(boss, worker);
            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    //检测读空闲事件
                    ch.pipeline().addLast(new IdleStateHandler(5,0,0));
                    ch.pipeline().addLast(new ChannelDuplexHandler(){
                        @Override
                        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
                            if (idleStateEvent.state()==IdleState.READER_IDLE){
                                log.debug("读事件超时了");
                                ctx.channel().close();
                            }
                        }
                    });
                    ch.pipeline().addLast(new ProcotolFrameDecoder());
                    ch.pipeline().addLast(LOGGING_HANDLER);
                    ch.pipeline().addLast(MESSAGE_CODEC);
                    ch.pipeline().addLast(loginRequestMessageHandler);
                    ch.pipeline().addLast(chatRequestMessageHandler);
                    ch.pipeline().addLast(groupCreateRequestMessageHandler);
                    ch.pipeline().addLast(groupChatRequestMessageHandler);
                    ch.pipeline().addLast(quitChandler);
                }
            });
            Channel channel = serverBootstrap.bind(8080).sync().channel();
            channel.closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("server error", e);
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }

}

3.客户端处理

但是有时我们有时候又确实在一端时间里面可能没有操作,比如上厕所,或则有电话。

那么在客户端同样也可以加入一个读写事件状态检测处理器,当检测我门客户端一段时间(这个时间必须小于服务端检测的时间)没有写入的时候就可以主动往服务器去发送一个心跳消息,向服务端表明我还在。

@Slf4j
public class ChatClient {
    public static void main(String[] args) {
        NioEventLoopGroup group = new NioEventLoopGroup();
        LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
        MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
        CountDownLatch LOGIN_WAIT=new CountDownLatch(1);
        try {
            final boolean[] flag = {false};
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.group(group);
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new ProcotolFrameDecoder());
//                    ch.pipeline().addLast(LOGGING_HANDLER);
                    ch.pipeline().addLast(MESSAGE_CODEC);
                    //检测写空闲事件,检测时间要比服务器的时间短
                    ch.pipeline().addLast(new IdleStateHandler(0,3,0));
                    ch.pipeline().addLast(new ChannelDuplexHandler(){
                        @Override
                        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
                            if (idleStateEvent.state()== IdleState.WRITER_IDLE){
                                ctx.writeAndFlush(new PingMessage());
                            }
                        }
                    });
                    ch.pipeline().addLast("clientHandler",new ChannelInboundHandlerAdapter(){
                        @Override
                        public void channelActive(ChannelHandlerContext ctx) throws Exception {

                            ExecutorService executorService = Executors.newSingleThreadExecutor();
                            try {
                                executorService.execute(()->{
                                    Scanner scanner = new Scanner(System.in);
                                    System.out.println("请输入用户名");
                                    String username = scanner.nextLine();
                                    System.out.println("请输入密码");
                                    String password = scanner.nextLine();
                                    LoginRequestMessage message = new LoginRequestMessage(username, password);
                                    ctx.writeAndFlush(message);
                                    System.out.println("等待");
                                    try {
                                        LOGIN_WAIT.await();

                                    } catch (InterruptedException e) {
                                        e.printStackTrace();
                                    }
                                    //登录失败
                                    if (!flag[0]){
                                        System.out.println("登录失败");
                                        ctx.channel().close();
                                        return;
                                    }
                                    //登录成功
                                    System.out.println("登录成功");
                                    while (true){
                                        System.out.println("========");
                                        System.out.println("send [to] [content]");
                                        String s = scanner.nextLine();
                                        String[] s1 = s.split(" ");
                                        switch (s1[0]){
                                            case "send":
                                                ctx.writeAndFlush(new ChatRequestMessage(username, s1[1], s1[2]));
                                                break;
                                            case "gsend":
                                                ctx.writeAndFlush(new GroupChatRequestMessage(username, s1[1], s1[2]));
                                                break;
                                            case "gcreate":
                                                Set<String> set = new HashSet<>(Arrays.asList(s1[2].split(",")));
                                                set.add(username); // 加入自己
                                                ctx.writeAndFlush(new GroupCreateRequestMessage(s1[1], set));
                                                break;
                                            case "gmembers":
                                                ctx.writeAndFlush(new GroupMembersRequestMessage(s1[1]));
                                                break;
                                            case "gjoin":
                                                ctx.writeAndFlush(new GroupJoinRequestMessage(username, s1[1]));
                                                break;
                                            case "gquit":
                                                ctx.writeAndFlush(new GroupQuitRequestMessage(username, s1[1]));
                                                break;
                                            case "quit":
                                                ctx.channel().close();
                                                return;
                                            default:
                                                System.out.println("我还没有这个功能");
                                                break;
                                        }
                                    }
                                });
                            } catch (Exception e) {
                                e.printStackTrace();
                            }finally {
                                executorService.shutdown();
                            }
                        }

                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                            if (msg instanceof LoginResponseMessage){
                                LoginResponseMessage message= (LoginResponseMessage) msg;
                                if (message.isSuccess()){
                                    flag[0] =true;
                                }
                                LOGIN_WAIT.countDown();
                            }
                            log.debug("{}",msg);
                        }
                    });
                }
            });
            Channel channel = bootstrap.connect("localhost", 8080).sync().channel();
            channel.closeFuture().sync();
        } catch (Exception e) {
            log.error("client error", e);
        } finally {
            group.shutdownGracefully();
        }
    }
}

Netty 中,实现心跳检测可以使用 IdleStateHandler 类。这个类是一个 ChannelHandler,可以在一段时间内检测 Channel 是否有读或写事件发生,如果超时没有发生事件,就会触发一个 IdleStateEvent 事件。我们可以在 ChannelPipeline 中添加 IdleStateHandler,并在 ChannelInboundHandler 中处理 IdleStateEvent 事件。 以下是一个简单的示例代码: ```java public class HeartbeatServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // 添加 IdleStateHandler,10 秒钟没有读事件,20 秒钟没有写事件,30 秒钟没有读写事件就会触发 IdleStateEvent 事件 pipeline.addLast(new IdleStateHandler(10, 20, 30, TimeUnit.SECONDS)); // 添加自定义的处理器,处理 IdleStateEvent 事件 pipeline.addLast(new HeartbeatServerHandler()); } } public class HeartbeatServerHandler extends ChannelInboundHandlerAdapter { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof IdleStateEvent) { IdleStateEvent e = (IdleStateEvent) evt; if (e.state() == IdleState.READER_IDLE) { // 10 秒钟没有读事件,可以认为客户端已经断开连接 ctx.close(); } else if (e.state() == IdleState.WRITER_IDLE) { // 20 秒钟没有写事件,发送心跳包 ctx.writeAndFlush(new HeartbeatMessage()); } else if (e.state() == IdleState.ALL_IDLE) { // 30 秒钟没有读写事件,可以认为客户端已经断开连接 ctx.close(); } } } } ``` 在上面的代码中,我们首先添加了一个 IdleStateHandler,指定了读超时时间、写超时时间和读写超时时间,然后添加了一个自定义的 ChannelInboundHandlerAdapter,重写了 userEventTriggered 方法,处理 IdleStateEvent 事件。在方法中,我们根据不同的 IdleState 处理不同的事件,例如读超时就关闭连接,写超时就发送心跳包。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值