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.");
                    // 在这里可以发送心跳消息给对方
                }
            }
        }
    }
}
相关推荐
Marst Code3 分钟前
(Django)初步使用
后端·python·django
代码之光_198010 分钟前
SpringBoot校园资料分享平台:设计与实现
java·spring boot·后端
编程老船长22 分钟前
第26章 Java操作Mongodb实现数据持久化
数据库·后端·mongodb
IT果果日记44 分钟前
DataX+Crontab实现多任务顺序定时同步
后端
姜学迁2 小时前
Rust-枚举
开发语言·后端·rust
爱学习的小健2 小时前
MQTT--Java整合EMQX
后端
北极小狐3 小时前
Java vs JavaScript:类型系统的艺术 - 从 Object 到 any,从静态到动态
后端
tangdou3690986553 小时前
两种方案手把手教你多种服务器使用tinyproxy搭建http代理
运维·后端·自动化运维
【D'accumulation】3 小时前
令牌主动失效机制范例(利用redis)注释分析
java·spring boot·redis·后端
2401_854391083 小时前
高效开发:SpringBoot网上租赁系统实现细节
java·spring boot·后端