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

本文详细探讨了Netty客户端在实现TCP长连接时的断线重连问题,包括如何监听到连接断开、如何实现重连以及如何避免重连异常。通过分析异常触发和心跳检查机制,提出两种断线重连方案,并讨论了客户端线程数量的合理配置。此外,文章还指出在IO线程上执行同步操作可能导致的问题,建议使用单独线程池或异步重连以避免资源浪费。
摘要由CSDN通过智能技术生成

前言

在实现TCP长连接功能中,客户端断线重连是一个很常见的问题,当我们使用netty实现断线重连时,是否考虑过如下几个问题:

  • 如何监听到客户端和服务端连接断开 ?
  • 如何实现断线后重新连接 ?
  • netty客户端线程给多大比较合理 ?

其实上面都是笔者在做断线重连时所遇到的问题,而 “netty客户端线程给多大比较合理?” 这个问题更是笔者在做断线重连时因一个异常引发的思考。下面讲讲整个过程:

因为本节讲解内容主要涉及在客户端,但是为了读者能够运行整个程序,所以这里先给出服务端及公共的依赖和实体类。

服务端及common代码

maven依赖:

<dependencies>
    <!--只是用到了spring-boot的日志框架-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.4.1</version>
    </dependency>

    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.56.Final</version>
    </dependency>

    <dependency>
        <groupId>org.jboss.marshalling</groupId>
        <artifactId>jboss-marshalling-serial</artifactId>
        <version>2.0.10.Final</version>
    </dependency>
</dependencies>

服务端业务处理代码

主要用于记录打印当前客户端连接数,当接收到客户端信息后返回“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。


                
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值