Netty作为服务端,前端断开连接,后端无感知,发送消息报错如何处理?

在常规的Http和WebSocket连接中,Netty无法及时感知到连接的关闭,连接的关闭通常是通过一定的超时机制或心跳检测来检测的。以下是一些可能得方法:

正常关闭-浏览器发送断开连接请求

通知

通过在前端的页面上监听beforeunloadunload等事件,当用户关闭页面时,可以发送请求通知服务器。

javascript 复制代码
window.addEventListener('beforeunload', function (event) {
    // 发送请求通知服务器
    // ...
});

接收到关闭消息-异步处理

需要注意的是,前端关闭事件通知到服务器的过程可能受到网络延迟和异步性的影响,因此服务器端的响应肯呢个不会立即发生,在处理这种情况时,通常需要考虑异步处理和适当的超时机制。

在 Netty 服务器端,前端通知关闭的过程通常不会直接触发 channelInactive 方法。而是通过 Netty 的 ChannelInboundHandler 中的 channelRead 方法来处理前端发送的关闭通知请求。

以下是一个简化的示例,展示了在 Netty 服务器端如何处理前端的关闭通知请求:

typescript 复制代码
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.DefaultFullHttpResponse;

public class MyHttpHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof FullHttpRequest) {
            FullHttpRequest request = (FullHttpRequest) msg;

            // 假设关闭通知的请求路径为 "/close"
            if ("/close".equals(request.uri())) {
                // 异步处理关闭通知
                ctx.channel().eventLoop().execute(() -> handleCloseNotification(ctx));
            } else {
                // 处理其他请求
                // ...
            }
        } else {
            // 处理非 HTTP 请求
            // ...
        }
    }

    private void handleCloseNotification(ChannelHandlerContext ctx) {
        // 在这里异步处理关闭通知的逻辑,例如释放资源、保存状态等
        System.out.println("Received close notification from frontend asynchronously.");

        // 异步响应关闭通知请求
        sendResponse(ctx, HttpResponseStatus.OK, "OK");
    }

    private void sendResponse(ChannelHandlerContext ctx, HttpResponseStatus status, String content) {
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);
        response.headers().set("Content-Type", "text/plain; charset=UTF-8");
        response.content().writeBytes(content.getBytes());

        // 发送响应
        ctx.writeAndFlush(response);
    }
}

在上述示例中,关闭通知的处理逻辑被放置在 handleCloseNotification 方法中,并通过 execute 方法提交给 eventLoop 异步执行。这样可以确保关闭通知的处理不会阻塞 Netty 的 IO 线程,从而保持应用程序的响应性。

客户端网络中断-异常关闭

服务端在进行消息发送时,连接已经失活,会有ChannelClosedException异常。

在 Netty 中,当发生异常时,通常会触发异常处理机制。Netty 的异常处理机制主要通过 ChannelPipeline 中的 ChannelHandler 来实现,其中 exceptionCaught 方法用于处理异常情况。

当一个异常发生时,Netty 会调用 exceptionCaught 方法,并将异常作为参数传递给该方法。具体的异常类型可以是 ChannelException 或其子类,例如 ChannelClosedException

以下是一个简单的示例,展示了如何在自定义的 ChannelHandler 中处理异常:

scala 复制代码
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class MyChannelHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 在这里处理异常情况
        if (cause instanceof ChannelClosedException) {
            // 处理ChannelClosedException
            System.err.println("Channel is closed: " + cause.getMessage());
        } else {
            // 处理其他异常
            cause.printStackTrace();
        }

        // 关闭连接或其他处理
        ctx.close();
    }
}

在上述示例中,exceptionCaught 方法中对异常进行了处理。如果异常是 ChannelClosedException 类型,表示通道已关闭,可以在这里处理相应的逻辑。对于其他类型的异常,可以选择记录日志、打印异常信息等处理方式。

你需要将这个自定义的 MyChannelHandler 添加到你的 ChannelPipeline 中,以便它能够处理通道的异常情况:

ini 复制代码
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new MyChannelHandler());

那如何在服务端进行主动判断连接失活并且做出相应的处理呢?

对于前后端一直存在交互的场景下可以使用检测空闲连接来实现

IdleStateHandler :Netty提供了IdleStateHandler,它可以用来检测空闲连接并触发相应的事件。通过在ChannelPipeline中添加IdleStateHandler,你可以设置检测的时间间隔和空闲超时时间。当连接空闲超过设定的时间时,会触发userEventTriggered事件,你可以在这个事件中处理连接关闭的逻辑。

java 复制代码
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;

import java.util.concurrent.TimeUnit;

public class NettyServer {

    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        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) {
                            ch.pipeline().addLast(new IdleStateHandler(0, 0, 5, TimeUnit.SECONDS)); // 设置空闲检测时间为5秒
                            ch.pipeline().addLast(new MyIdleStateHandler()); // 自定义处理空闲状态的Handler
                        }
                    })
                    .bind(8080)
                    .sync()
                    .channel()
                    .closeFuture()
                    .sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    // 自定义处理空闲状态的Handler
    private static class MyIdleStateHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
            if (evt instanceof IdleStateEvent) {
                IdleStateEvent event = (IdleStateEvent) evt;
                if (event.state() == IdleState.READER_IDLE) {
                    // 读超时,即在指定时间内没有接收到数据
                    System.out.println("Reader idle, closing the connection.");
                    ctx.close();
                } else if (event.state() == IdleState.WRITER_IDLE) {
                    // 写超时,即在指定时间内没有发送数据
                    System.out.println("Writer idle, sending heartbeat.");
                    // 在这里可以发送心跳消息给对方
                }
            }
        }
    }
}
相关推荐
瓜牛_gn20 分钟前
依赖注入注解
java·后端·spring
Estar.Lee38 分钟前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
喜欢猪猪39 分钟前
Django:从入门到精通
后端·python·django
一个小坑货39 分钟前
Cargo Rust 的包管理器
开发语言·后端·rust
bluebonnet2744 分钟前
【Rust练习】22.HashMap
开发语言·后端·rust
uhakadotcom1 小时前
如何实现一个基于CLI终端的AI 聊天机器人?
后端
Iced_Sheep2 小时前
干掉 if else 之策略模式
后端·设计模式
XINGTECODE2 小时前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
程序猿进阶2 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺2 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端