Netty客户端断线重连实现及问题思考

服务端业务处理代码

=========

主要用于记录打印当前客户端连接数,当接收到客户端信息后返回“hello netty”字符串

@ChannelHandler.Sharable

public class SimpleServerHandler extends ChannelInboundHandlerAdapter {

private static final InternalLogger log = InternalLoggerFactory.getInstance(SimpleServerHandler.class);

public static final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

@Override

public void channelActive(ChannelHandlerContext ctx) throws Exception {

channels.add(ctx.channel());

log.info(“客户端连接成功: client address :{}”, ctx.channel().remoteAddress());

log.info(“当前共有{}个客户端连接”, channels.size());

}

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

log.info(“server channelRead:{}”, msg);

ctx.channel().writeAndFlush(“hello netty”);

}

@Override

public void channelInactive(ChannelHandlerContext ctx) throws Exception {

log.info(“channelInactive: client close”);

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

if (cause instanceof java.io.IOException) {

log.warn(“exceptionCaught: client close”);

} else {

cause.printStackTrace();

}

}

}

服务端心跳检查代码

=========

当接收心跳"ping"信息后,返回客户端’'pong"信息。如果客户端在指定时间内没有发送任何信息则关闭客户端。

public class ServerHeartbeatHandler extends ChannelInboundHandlerAdapter {

private static final InternalLogger log = InternalLoggerFactory.getInstance(ServerHeartbeatHandler.class);

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

log.info(“server channelRead:{}”, msg);

if (msg.equals(“ping”)) {

ctx.channel().writeAndFlush(“pong”);

} else {

//由下一个handler处理,示例中则为SimpleServerHandler

ctx.fireChannelRead(msg);

}

}

@Override

public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

if (evt instanceof IdleStateEvent) {

//该事件需要配合 io.netty.handler.timeout.IdleStateHandler使用

IdleStateEvent idleStateEvent = (IdleStateEvent) evt;

if (idleStateEvent.state() == IdleState.READER_IDLE) {

//超过指定时间没有读事件,关闭连接

log.info(“超过心跳时间,关闭和服务端的连接:{}”, ctx.channel().remoteAddress());

//ctx.channel().close();

}

} else {

super.userEventTriggered(ctx, evt);

}

}

}

编解码工具类

======

主要使用jboss-marshalling-serial编解码工具,可自行查询其优缺点,这里只是示例使用。

public final class MarshallingCodeFactory {

/** 创建Jboss marshalling 解码器 */

public static MarshallingDecoder buildMarshallingDecoder() {

//参数serial表示创建的是Java序列化工厂对象,由jboss-marshalling-serial提供

MarshallerFactory factory = Marshalling.getProvidedMarshallerFactory(“serial”);

MarshallingConfiguration configuration = new MarshallingConfiguration();

configuration.setVersion(5);

DefaultUnmarshallerProvider provider = new DefaultUnmarshallerProvider(factory, configuration);

return new MarshallingDecoder(provider, 1024);

}

/** 创建Jboss marshalling 编码器 */

public static MarshallingEncoder buildMarshallingEncoder() {

MarshallerFactory factory = Marshalling.getProvidedMarshallerFactory(“serial”);

MarshallingConfiguration configuration = new MarshallingConfiguration();

configuration.setVersion(5);

DefaultMarshallerProvider provider = new DefaultMarshallerProvider(factory, configuration);

return new MarshallingEncoder(provider);

}

}

公共实体类

=====

public class UserInfo implements Serializable {

private static final long serialVersionUID = 6271330872494117382L;

private String username;

private int age;

public UserInfo() {

}

public UserInfo(String username, int age) {

this.username = username;

this.age = age;

}

//省略getter/setter/toString

}

下面开始本文的重点,客户端断线重连以及问题思考。

客户端实现

=====

  • 刚开始启动时需要进行同步连接,指定连接次数内没用通过则抛出异常,进程退出。

  • 客户端启动后,开启定时任务,模拟客户端数据发送。

客户端业务处理handler,接收到数据后,通过日志打印。

public class SimpleClientHandler extends ChannelInboundHandlerAdapter {

private static final InternalLogger log = InternalLoggerFactory.getInstance(SimpleClientHandler.class);

private NettyClient client;

public SimpleClientHandler(NettyClient client) {

this.client = client;

}

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

log.info(“client receive:{}”, msg);

}

}

封装连接方法、断开连接方法、getChannel()返回io.netty.channel.Channel用于向服务端发送数据。boolean connect()是一个同步连接方法,如果连接成功返回true,连接失败返回false。

public class NettyClient {

private static final InternalLogger log = InternalLoggerFactory.getInstance(NettyClient.class);

private EventLoopGroup workerGroup;

private Bootstrap bootstrap;

private volatile Channel clientChannel;

public NettyClient() {

this(-1);

}

public NettyClient(int threads) {

workerGroup = threads > 0 ? new NioEventLoopGroup(threads) : new NioEventLoopGroup();

bootstrap = new Bootstrap();

bootstrap.group(workerGroup)

.channel(NioSocketChannel.class)

.option(ChannelOption.TCP_NODELAY, true)

.option(ChannelOption.SO_KEEPALIVE, false)

.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30000)

.handler(new ClientHandlerInitializer(this));

}

public boolean connect() {

log.info(“尝试连接到服务端: 127.0.0.1:8088”);

try {

ChannelFuture channelFuture = bootstrap.connect(“127.0.0.1”, 8088);

boolean notTimeout = channelFuture.awaitUninterruptibly(30, TimeUnit.SECONDS);

clientChannel = channelFuture.channel();

if (notTimeout) {

if (clientChannel != null && clientChannel.isActive()) {

log.info(“netty client started !!! {} connect to server”, clientChannel.localAddress());

return true;

}

Throwable cause = channelFuture.cause();

if (cause != null) {

exceptionHandler(cause);

}

} else {

log.warn(“connect remote host[{}] timeout {}s”, clientChannel.remoteAddress(), 30);

}

} catch (Exception e) {

exceptionHandler(e);

}

clientChannel.close();

return false;

}

private void exceptionHandler(Throwable cause) {

if (cause instanceof ConnectException) {

log.error(“连接异常:{}”, cause.getMessage());

} else if (cause instanceof ClosedChannelException) {

log.error(“connect error:{}”, “client has destroy”);

} else {

log.error(“connect error:”, cause);

}

}

public void close() {

if (clientChannel != null) {

clientChannel.close();

}

if (workerGroup != null) {

workerGroup.shutdownGracefully();

}

}

public Channel getChannel() {

return clientChannel;

}

static class ClientHandlerInitializer extends ChannelInitializer {

private static final InternalLogger log = InternalLoggerFactory.getInstance(NettyClient.class);

private NettyClient client;

public ClientHandlerInitializer(NettyClient client) {

this.client = client;

}

@Override

protected void initChannel(SocketChannel ch) throws Exception {

ChannelPipeline pipeline = ch.pipeline();

pipeline.addLast(MarshallingCodeFactory.buildMarshallingDecoder());

pipeline.addLast(MarshallingCodeFactory.buildMarshallingEncoder());

//pipeline.addLast(new IdleStateHandler(25, 0, 10));

//pipeline.addLast(new ClientHeartbeatHandler());

pipeline.addLast(new SimpleClientHandler(client));

}

}

}

客户端启动类

public class NettyClientMain {

private static final InternalLogger log = InternalLoggerFactory.getInstance(NettyClientMain.class);

private static final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();

public static void main(String[] args) {

NettyClient nettyClient = new NettyClient();

boolean connect = false;

//刚启动时尝试连接10次,都无法建立连接则不在尝试

//如果想在刚启动后,一直尝试连接,需要放在线程中,异步执行,防止阻塞程序

for (int i = 0; i < 10; i++) {

connect = nettyClient.connect();

if (connect) {

break;

}

//连接不成功,隔5s之后重新尝试连接

try {

Thread.sleep(5000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

if (connect) {

log.info(“定时发送数据”);

send(nettyClient);

} else {

nettyClient.close();

log.info(“进程退出”);

}

}

/** 定时发送数据 */

static void send(NettyClient client) {

scheduledExecutor.schedule(new SendTask(client,scheduledExecutor), 2, TimeUnit.SECONDS);

}

}

客户端断线重连

=======

断线重连需求:

  • 服务端和客户端之间网络异常,或响应超时(例如有个很长时间的fullGC),客户端需要主动重连其他节点。

  • 服务端宕机时或者和客户端之间发生任何异常时,客户端需要主动重连其他节点。

  • 服务端主动向客户端发送(服务端)下线通知时,客户端需要主动重连其他节点。

如何监听到客户端和服务端连接断开 ?

==================

netty的io.netty.channel.ChannelInboundHandler接口中给我们提供了许多重要的接口方法。为了避免实现全部的接口方法,可以通过继承io.netty.channel.ChannelInboundHandlerAdapter来重写相应的方法即可。

1.void channelInactive(ChannelHandlerContext ctx);在客户端关闭时被调用,表示客户端断开连接。当有以下几种情况发生时会触发:

  • 客户端在正常active状态下,主动调用channel或者ctx的close方法。

  • 服务端主动调用channel或者ctx的close方法关闭客户端的连接 。

  • 发生java.io.IOException(一般情况下是双方连接断开)或者java.lang.OutOfMemoryError(4.1.52版本中新增)时

2.void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;则是在入栈发生任何异常时被调用。如果异常是java.io.IOException或者java.lang.OutOfMemoryError(4.1.52版本新增)时,还会触发channelInactive方法,也就是上面channelInactive被触发的第3条情况。

3.心跳检查也是检查客户端与服务端之间连接状态的必要方式,因为在一些状态下,两端实际上已经断开连接,但客户端无法感知,这时候就需要通过心跳来判断两端的连接状态。心跳可以是客户端心跳和服务端心跳。

  • 客户端信跳:即为客户端发送心跳ping信息,服务端回复pong信息。这样在指定时间内,双方有数据交互则认为是正常连接状态。

  • 服务端信息:则是服务端向客户端发送ping信息,客户端回复pong信息。在指定时间内没有收到回复,则认为对方下线。

netty给我们提供了非常简单的心跳检查方式,只需要在channel的handler链上,添加io.netty.handler.timeout.IdleStateHandler即可实现。

IdleStateHandler有如下几个重要的参数:

  • readerIdleTimeSeconds, 读超时. 即当在指定的时间间隔内没有从 Channel 读取到数据时, 会触发一个READER_IDLE的IdleStateEvent 事件.

  • writerIdleTimeSeconds, 写超时. 即当在指定的时间间隔内没有数据写入到 Channel 时, 会触发一个WRITER_IDLE的IdleStateEvent 事件.

  • allIdleTimeSeconds, 读/写超时. 当在指定的时间间隔内没有读或写操作时, 会触发一个ALL_IDLE的IdleStateEvent 事件.

为了能够监听到这些事件的触发,还需要重写ChannelInboundHandler#userEventTriggered(ChannelHandlerContext ctx, Object evt)方法,通过参数evt判断事件类型。在指定的时间内如果没有读写则发送一条心跳的ping请求,在指定时间内没有收到读操作则任务已经和服务端断开连接。则调用channel或者ctx的close方法,使客户端Handler执行channelInactive方法。

到这里看来我们只要在channelInactive和exceptionCaught两个方法中实现自己的重连逻辑即可,但是笔者遇到了第一个坑,重连方法执行了两次。

先看示例代码和结果,在com.bruce.netty.rpc.client.SimpleClientHandler中添加如下代码:

public class SimpleClientHandler extends ChannelInboundHandlerAdapter {

private static final InternalLogger log = InternalLoggerFactory.getInstance(SimpleClientHandler.class);

//省略部分代码…

/** 客户端正常下线时执行该方法 */

@Override

public void channelInactive(ChannelHandlerContext ctx) throws Exception {

log.warn(“channelInactive:{}”, ctx.channel().localAddress());

reconnection(ctx);

}

/** 入栈发生异常时执行exceptionCaught */

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

if (cause instanceof IOException) {

log.warn(“exceptionCaught:客户端[{}]和远程断开连接”, ctx.channel().localAddress());

} else {

log.error(cause);

}

reconnection(ctx);

}

private void reconnection(ChannelHandlerContext ctx) {

log.info(“5s之后重新建立连接”);

//暂时为空实现

}

}

ClientHandlerInitializer 中添加io.netty.handler.timeout.IdleStateHandler用于心跳检查,ClientHeartbeatHandler用于监听心跳事件,接收心跳pong回复。

static class ClientHandlerInitializer extends ChannelInitializer {

private static final InternalLogger log = InternalLoggerFactory.getInstance(NettyClient.class);

private NettyClient client;

public ClientHandlerInitializer(NettyClient client) {

this.client = client;

}

@Override

protected void initChannel(SocketChannel ch) throws Exception {

ChannelPipeline pipeline = ch.pipeline();

pipeline.addLast(MarshallingCodeFactory.buildMarshallingDecoder());

pipeline.addLast(MarshallingCodeFactory.buildMarshallingEncoder());

//25s内没有read操作则触发READER_IDLE事件

//10s内既没有read又没有write操作则触发ALL_IDLE事件

pipeline.addLast(new IdleStateHandler(25, 0, 10));

pipeline.addLast(new ClientHeartbeatHandler());

pipeline.addLast(new SimpleClientHandler(client));

}

}

com.bruce.netty.rpc.client.ClientHeartbeatHandler

public class ClientHeartbeatHandler extends ChannelInboundHandlerAdapter {

private static final InternalLogger log = InternalLoggerFactory.getInstance(ClientHeartbeatHandler.class);

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

if (msg.equals(“pong”)) {

log.info(“收到心跳回复”);

} else {

super.channelRead(ctx, msg);

}

}

@Override

public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

if (evt instanceof IdleStateEvent) {

//该事件需要配合 io.netty.handler.timeout.IdleStateHandler使用

IdleStateEvent idleStateEvent = (IdleStateEvent) evt;

if (idleStateEvent.state() == IdleState.ALL_IDLE) {

//向服务端发送心跳检测

ctx.writeAndFlush(“ping”);

log.info(“发送心跳数据”);

} else if (idleStateEvent.state() == IdleState.READER_IDLE) {

//超过指定时间没有读事件,关闭连接

log.info(“超过心跳时间,关闭和服务端的连接:{}”, ctx.channel().remoteAddress());

ctx.channel().close();

}

} else {

super.userEventTriggered(ctx, evt);

}

}

}

先启动server端,再启动client端,待连接成功之后kill掉 server端进程。

Netty客户端断线重连实现及问题思考

通过客户端日志可以看出,先是执行了exceptionCaught方法然后执行了channelInactive方法,但是这两个方法中都调用了reconnection方法,导致同时执行了两次重连。

为什么执行了exceptionCaught方法又执行了channelInactive方法呢?

我们可以在exceptionCaught和channelInactive方法添加断点一步步查看源码

Netty客户端断线重连实现及问题思考

当NioEventLoop执行select操作之后,处理相应的SelectionKey,发生异常后,会调用AbstractNioByteChannel.NioByteUnsafe#handleReadException方法进行处理,并触发pipeline.fireExceptionCaught(cause),最终调用到用户handler的fireExceptionCaught方法。

private void handleReadException(ChannelPipeline pipeline, ByteBuf byteBuf, Throwable cause, boolean close,

RecvByteBufAllocator.Handle allocHandle) {

if (byteBuf != null) {

if (byteBuf.isReadable()) {

readPending = false;

pipeline.fireChannelRead(byteBuf);

} else {

byteBuf.release();

}

}

allocHandle.readComplete();

pipeline.fireChannelReadComplete();

pipeline.fireExceptionCaught(cause);

// If oom will close the read event, release connection.

// See https://github.com/netty/netty/issues/10434

if (close || cause instanceof OutOfMemoryError || cause instanceof IOException) {

closeOnRead(pipeline);

}

}

该方法最后会判断异常类型,执行close连接的方法。在连接断线的场景中,这里即为java.io.IOException,所以执行了close方法,当debug到AbstractChannel.AbstractUnsafe#close(ChannelPromise, Throwable, ClosedChannelException, notify)方法中会发现最后又调用了AbstractUnsafe#fireChannelInactiveAndDeregister方法,继续debug最后则会执行自定义的fireChannelInactive方法。

到这里可以总结一个知识点:netty中当执行到handler地fireExceptionCaught方法时,可能会继续触发到fireChannelInactive,也可能不会触发fireChannelInactive。

除了netty根据异常类型判断是否执行close方法外,其实开发人员也可以自己通过ctx或者channel去调用close方法,代码如下:

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

if (cause instanceof IOException) {

log.warn(“exceptionCaught:客户端[{}]和远程断开连接”, ctx.channel().localAddress());

} else {

log.error(cause);

}

//ctx.close();

ctx.channel().close();

}

但这种显示调用close方法,是否一定会触发调用fireChannelInactive呢?

如果是,那么只需要在exceptionCaught中调用close方法,fireChannelInactive中做重连的逻辑即可!!

在笔者通过日志观察到,在exceptionCaught中调用close方法每次都会调用fireChannelInactive方法。但是查看源码,笔者认为这是不一定的,因为在AbstractChannel.AbstractUnsafe#close(ChannelPromise,Throwable, ClosedChannelException, notify)中会调用io.netty.channel.Channel#isActive进行判断,只有为true,才会执行fireChannelInactive方法。

//io.netty.channel.socket.nio.NioSocketChannel#isActive

@Override

public boolean isActive() {

SocketChannel ch = javaChannel();

return ch.isOpen() && ch.isConnected();

}

如何解决同时执行两次问题呢?

在netty初始化时,我们都会添加一系列的handler处理器,这些handler实际上会在netty创建Channel对象(NioSocketChannel)时,被封装在DefaultChannelPipeline中,而DefaultChannelPipeline实际上是一个双向链表,头节点为TailContext,尾节点为TailContext,而中间的节点则是我们添加的一个个handler(被封装成DefaultChannelHandlerContext),当执行Pipeline上的方法时,会从链表上遍历handler执行,因此当执行exceptionCaught方法时,我们只需要提前移除链表上自定义的Handler则无法执行fireChannelInactive方法。

Netty客户端断线重连实现及问题思考

最后实现代码如下:

public class SimpleClientHandler extends ChannelInboundHandlerAdapter {

private static final InternalLogger log = InternalLoggerFactory.getInstance(SimpleClientHandler.class);

@Override

public void channelInactive(ChannelHandlerContext ctx) throws Exception {

log.warn(“channelInactive:{}”, ctx.channel().localAddress());

ctx.pipeline().remove(this);

ctx.channel().close();

reconnection(ctx);

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

if (cause instanceof IOException) {

log.warn(“exceptionCaught:客户端[{}]和远程断开连接”, ctx.channel().localAddress());

} else {

log.error(cause);

}

ctx.pipeline().remove(this);

//ctx.close();

ctx.channel().close();

reconnection(ctx);

}

}

执行效果如下,可以看到当发生异常时,只是执行了exceptionCaught方法,并且通过channel关闭了上一次连接资源,也没有执行当前handler的fireChannelInactive方法。

Netty客户端断线重连实现及问题思考

如何实现断线后重新连接 ?

=============

通过上面分析,我们已经知道在什么方法中实现自己的重连逻辑,但是具体该怎么实现呢,怀着好奇的心态搜索了一下各大码友的实现方案。大多做法是通过ctx.channel().eventLoop().schedule添加一个定时任务调用客户端的连接方法。笔者也参考该方式实现代码如下:

private void reconnection(ChannelHandlerContext ctx) {

log.info(“5s之后重新建立连接”);

ctx.channel().eventLoop().schedule(new Runnable() {

@Override

public void run() {

boolean connect = client.connect();

if (connect) {

log.info(“重新连接成功”);

} else {

reconnection(ctx);

}

}

}, 5, TimeUnit.SECONDS);

}

测试:先启动server端,再启动client端,待连接成功之后kill掉 server端进程。客户端如期定时执行重连,但也就去茶水间倒杯水的时间,回来后发现了如下异常。

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
一下各大码友的实现方案。大多做法是通过ctx.channel().eventLoop().schedule添加一个定时任务调用客户端的连接方法。笔者也参考该方式实现代码如下:

private void reconnection(ChannelHandlerContext ctx) {

log.info(“5s之后重新建立连接”);

ctx.channel().eventLoop().schedule(new Runnable() {

@Override

public void run() {

boolean connect = client.connect();

if (connect) {

log.info(“重新连接成功”);

} else {

reconnection(ctx);

}

}

}, 5, TimeUnit.SECONDS);

}

测试:先启动server端,再启动client端,待连接成功之后kill掉 server端进程。客户端如期定时执行重连,但也就去茶水间倒杯水的时间,回来后发现了如下异常。

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-wfqZPNEM-1714934172759)]

[外链图片转存中…(img-6UIheLjn-1714934172759)]

[外链图片转存中…(img-vBS49ag3-1714934172759)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 12
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netty 中,实现客户端重连可以通过以下几个步骤: 1. 创建一个连接管理器类来管理客户端的连接状态。 2. 在连接管理器中,创建一个定时任务,用于定期检查连接状态并重连。 3. 当连接断开时,通过监听器或回调方法得到通知。 4. 在断开的情况下,触发重连逻辑,重新连接到服务器。 下面是一个简单的示例代码,演示了如何在 Netty 客户端实现重连: ```java import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import java.util.concurrent.TimeUnit; public class ReconnectClient { private final String host; private final int port; private final Bootstrap bootstrap; private final EventLoopGroup eventLoopGroup; private volatile boolean reconnecting; public ReconnectClient(String host, int port) { this.host = host; this.port = port; this.bootstrap = new Bootstrap(); this.eventLoopGroup = new NioEventLoopGroup(); this.reconnecting = false; bootstrap.group(eventLoopGroup) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ch.pipeline().addLast(new MyHandler()); } }); } public void connect() { try { ChannelFuture future = bootstrap.connect(host, port).sync(); future.channel().closeFuture().sync(); } catch (InterruptedException e) { // 处理异常 } finally { if (!reconnecting) { eventLoopGroup.shutdownGracefully(); } } } public void reconnect() { if (reconnecting) { return; } reconnecting = true; eventLoopGroup.schedule(() -> { try { ChannelFuture future = bootstrap.connect(host, port).sync(); future.channel().closeFuture().sync(); } catch (InterruptedException e) { // 处理异常 } finally { reconnecting = false; reconnect(); // 递归调用重新连接 } }, 5, TimeUnit.SECONDS); } public static void main(String[] args) { ReconnectClient client = new ReconnectClient("localhost", 8080); client.connect(); } } class MyHandler extends ChannelInboundHandlerAdapter { @Override public void channelInactive(ChannelHandlerContext ctx) { // 连接断开时的处理逻辑 ReconnectClient client = new ReconnectClient("localhost", 8080); client.reconnect(); } // 其他处理方法... } ``` 上述示例中,`ReconnectClient` 类封装了客户端的连接管理和重连逻辑。在 `connect` 方法中,首次建立连接并等待关闭;当连接断开时,会触发 `channelInactive` 方法,在该方法中调用 `reconnect` 方法进行重连。`reconnect` 方法使用定时任务调度,在一定时间后尝试重新连接,并通过递归调用实现了持续的重连。 这只是一个简单的示例,并未考虑异常处理、连接失败的情况等。在实际应用中,你可能需要根据具体需求进行适当的修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值