Netty 断线重连方案实现

本文介绍了如何使用Netty处理长连接服务的断线问题,包括心跳机制检测连接状态、启动时连接重试以及运行中连接断开时的重连策略。通过设置IdleStateHandler和定制ChannelInboundHandler,确保服务可用性和稳定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自Netty 断线重连解决方案 - 走看看 (zoukankan.com)

用Netty实现长连接服务,当发生下面的情况时,会发生断线的情况。

  • 网络问题
  • 客户端启动时服务端挂掉了,连接不上服务端
  • 客户端已经连接服务端,服务端突然挂掉了
  • 其它问题等...

##如何解决上面的问题?

1.心跳机制检测连接存活

长连接是指建立的连接长期保持,不管有无数据包的发送都要保持连接通畅。心跳是用来检测一个系统是否存活或者网络链路是否通畅的一种方式,一般的做法是客户端定时向服务端发送心跳包,服务端收到心跳包后进行回复,客户端收到回复说明服务端存活。

通过心跳检测机制,可以检测客户端与服务的长连接是否保持,当客户端发送的心跳包没有收到服务端的响应式,可以认为服务端已经出故障了,这个时候可以重新连接或者选择其他的可用的服务进行连接。

在Netty中提供了一个IdleStateHandler类用于心跳检测,用法如下:

ch.pipeline().addLast("ping", new IdleStateHandler(60, 20, 60 * 10, TimeUnit.SECONDS));
  • 第一个参数 60 表示读操作空闲时间
  • 第二个参数 20 表示写操作空闲时间
  • 第三个参数 60*10 表示读写操作空闲时间
  • 第四个参数 单位/秒

在处理数据的ClientPoHandlerProto中增加userEventTriggered用来接收心跳检测结果,event.state()的状态分别对应上面三个参数的时间设置,当满足某个时间的条件时会触发事件。

public class ClientPoHandlerProto extends ChannelInboundHandlerAdapter {
    private ImConnection imConnection = new ImConnection();

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        MessageProto.Message message = (MessageProto.Message) msg;
        System.out.println("client:" + message.getContent());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state().equals(IdleState.READER_IDLE)) {
                System.out.println("长期没收到服务器推送数据");
                //可以选择重新连接
            } else if (event.state().equals(IdleState.WRITER_IDLE)) {
                System.out.println("长期未向服务器发送数据");
                //发送心跳包
                ctx.writeAndFlush(MessageProto.Message.newBuilder().setType(1));
            } else if (event.state().equals(IdleState.ALL_IDLE)) {
                System.out.println("ALL");
            }
        }
    }
}

服务端收到客户端发送的心跳消息后,回复一条信息

public class ServerPoHandlerProto extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        MessageProto.Message message = (MessageProto.Message) msg;
        if (ConnectionPool.getChannel(message.getId()) == null) {
            ConnectionPool.putChannel(message.getId(), ctx);
        }
        System.err.println("server:" + message.getId());
        // ping
        if (message.getType() == 1) {
            ctx.writeAndFlush(message);
        }

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

当客户端20秒没往服务端发送过数据,就会触发IdleState.WRITER_IDLE事件,这个时候我们就像服务端发送一条心跳数据,跟业务无关,只是心跳。服务端收到心跳之后就会回复一条消息,表示已经收到了心跳的消息,只要收到了服务端回复的消息,那么就不会触发IdleState.READER_IDLE事件,如果触发了IdleState.READER_IDLE事件就说明服务端没有给客户端响应,这个时候可以选择重新连接。

2.启动时连接重试

在Netty中实现重连的操作比较简单,Netty已经封装好了,我们只需要稍微扩展一下即可。

连接的操作是客户端这边执行的,重连的逻辑也得加在客户端,首先我们来看启动时要是连接不上怎么去重试

增加一个负责重试逻辑的监听器,代码如下:

import java.util.concurrent.TimeUnit;

import com.netty.im.client.ImClientApp;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.EventLoop;
/**
 * 负责监听启动时连接失败,重新连接功能
 * @author yinjihuan
 *
 */
public class ConnectionListener implements ChannelFutureListener {

    private ImConnection imConnection = new ImConnection();

    @Override
    public void operationComplete(ChannelFuture channelFuture) throws Exception {
        if (!channelFuture.isSuccess()) {
            final EventLoop loop = channelFuture.channel().eventLoop();
            loop.schedule(new Runnable() {
                @Override
                public void run() {
                    System.err.println("服务端链接不上,开始重连操作...");
                    imConnection.connect(ImClientApp.HOST, ImClientApp.PORT);
                }
            }, 1L, TimeUnit.SECONDS);
        } else {
            System.err.println("服务端链接成功...");
        }
    }
}

通过channelFuture.isSuccess()可以知道在连接的时候是成功了还是失败了,如果失败了我们就启动一个单独的线程来执行重新连接的操作。

只需要在ConnectionListener添加到ChannelFuture中去即可使用

public class ImConnection {

    private Channel channel;

    public Channel connect(String host, int port) {
        doConnect(host, port);
        return this.channel;
    }

    private void doConnect(String host, int port) {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup);
            b.channel(NioSocketChannel.class);
            b.option(ChannelOption.SO_KEEPALIVE, true);
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {

                    // 实体类传输数据,protobuf序列化
                    ch.pipeline().addLast("decoder",  
                            new ProtobufDecoder(MessageProto.Message.getDefaultInstance()));  
                    ch.pipeline().addLast("encoder",  
                            new ProtobufEncoder());  
                    ch.pipeline().addLast(new ClientPoHandlerProto());

                }
            });

            ChannelFuture f = b.connect(host, port);
            f.addListener(new ConnectionListener());
            channel = f.channel();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

}

可以按照如下步骤进行测试:

  • 直接启动客户端,不启动服务端
  • 当连接失败的时候会进入ConnectionListener中的operationComplete方法执行我们的重连逻辑

3.运行中连接断开时重试

使用的过程中服务端突然挂了,就得用另一种方式来重连了,可以在处理数据的Handler中进行处理。

public class ClientPoHandlerProto extends ChannelInboundHandlerAdapter {
    private ImConnection imConnection = new ImConnection();

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        MessageProto.Message message = (MessageProto.Message) msg;
        System.out.println("client:" + message.getContent());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.err.println("掉线了...");
        //使用过程中断线重连
        final EventLoop eventLoop = ctx.channel().eventLoop();
        eventLoop.schedule(new Runnable() {
            @Override
            public void run() {
                imConnection.connect(ImClientApp.HOST, ImClientApp.PORT);
            }
        }, 1L, TimeUnit.SECONDS);
        super.channelInactive(ctx);
    }

}

在连接断开时都会触发 channelInactive 方法, 处理重连的逻辑跟上面的一样。

可以按照如下步骤进行测试:

  • 启动服务端
  • 启动客户端,连接成功
  • 停掉服务端就会触发channelInactive进行重连操作
### Netty框架中实现断线连的解决方案Netty框架中,为了实现网络故障或服务端不可用的情况下能够自动连的功能,通常会在`ChannelInboundHandlerAdapter`类中覆盖`channelInactive`方法。该方法在网络连接被关闭或者处于不活跃状态时触发[^1]。 以下是具体的实现方式以及示例代码: #### 方法说明 当检测到连接丢失时,在`channelInactive`方法中调用自定义的连逻辑。需要注意的是,如果仅依赖`exceptionCaught`方法,则可能无法完全捕获所有的断开场景,因此推荐使用`channelInactive`作为主要入口[^2]。 另外,可以利用定时任务配合循环尝试建立新的连接直到成功为止。这种方式适用于长时间未恢复的服务端情况。 #### 示例代码 下面是一个基于Netty客户端实现断线连功能的具体例子: ```java public class ReconnectClientInitializer extends ChannelInitializer<SocketChannel> { private final EventLoopGroup group; public ReconnectClientInitializer(EventLoopGroup group){ this.group = group; } @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // 添加编解码器和其他处理器... pipeline.addLast(new ObjectEncoder()); pipeline.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null))); // 自定义业务处理handler, 负责监听并响应事件. pipeline.addLast(new SimpleChannelInboundHandler<Object>() { @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { super.channelInactive(ctx); System.out.println("Connection lost! Attempting to reconnect..."); // 创建一个新的Bootstrap实例用于新初始化连接流程 Bootstrap bootstrap = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new ReconnectClientInitializer(group)); connect(bootstrap); // 执行实际的连操作 } private void connect(Bootstrap b){ b.connect("localhost", 8080).addListener((ChannelFutureListener) future -> { if(future.isSuccess()){ System.out.println("Reconnected successfully!"); }else{ System.err.println("Failed to reconnect. Retrying in 5 seconds..."); // 如果失败则延迟一段时间再次尝试 group.schedule(() -> connect(b), 5, TimeUnit.SECONDS); } }); } }); } } ``` 此代码片段展示了如何设置一个基本的Netty客户端,并在其失去与服务器之间的联系时启动自动试机制[^1]。 #### 注意事项 - **资源管理**: 确保每次新创建的通道都正确释放旧有资源以防内存泄漏。 - **超时控制**: 设置合理的最大试次数和间隔时间以防止无限期等待。 - **日志记录**: 增加详细的错误信息输出有助于调试生产环境下的问题[^3]. ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值