Netty in Android application development in actual combat series (three) --- heartbeat processing | reconnection

Read the article suggests looking back from the beginning of the first chapter

This series of articles

First, the client adds heartbeat process

Here it is necessary to mention a netty important IdleStateHandlerfor processing the heartbeat mechanism; connection passage is provided for the current read, write, write idle timeout time has reached the set time when it will callback ClientHandlerin userEventTriggered(ChannelHandlerContext ctx, Object evt)function.

Here to explain what is meant by Idle Timeout: Suppose you set up a client read timeout of 10s, 10s continued if no data is received within the client (that is, the server does not send data over) then it will callback userEventTriggeredfunction, if there has been received the data then no callback userEventTriggeredfunction; continued until the 10s did not receive the data continue to trigger userEventTriggeredfunctions.

  • IdleStateHandler()Interpretation common constructors, the three parameters
public IdleStateHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds) {
        this((long)readerIdleTimeSeconds, (long)writerIdleTimeSeconds, (long)allIdleTimeSeconds, TimeUnit.SECONDS);
    }
  • When reading long idle readerIdleTimeSeconds
  • writerIdleTimeSeconds idle time to write long
  • When read long idle writerIdleTimeSeconds

IdleStateHandler use

  • And add codecs before, to ChannelPipelineadd to
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap()
        // 省略部分代码
        .handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                ChannelPipeline pipeline = socketChannel.pipeline();
                //添加心跳处理Handler
                pipeline.addLast(new IdleStateHandler(10, 0, 0));
                //省略部分代码
            }
        });
// 连接到服务端
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress(IP, PORT));
  • ClientHandlerTransmitting the heartbeat data packet
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    super.userEventTriggered(ctx, evt);
    if (evt instanceof IdleStateEvent) {
        if (((IdleStateEvent) evt).state() == IdleState.READER_IDLE) {
            sendHeartPkg(ctx);
        }
    } else {
        super.userEventTriggered(ctx, evt);
    }
}
/**
 * 发送心跳
 */
private void sendHeartPkg(ChannelHandlerContext ctx) {
    PkgDataBean bean = new PkgDataBean();
    bean.setCmd((byte) 0x02);
    bean.setData("心跳数据包");
    bean.setDataLength((byte) bean.getData().getBytes().length);
    ctx.channel().writeAndFlush(bean);
    Log.d(TAG, "客户端发送心跳成功");
}
  • The effect of running on the client
    Here Insert Picture Description
  • The effect of the server running
    Here Insert Picture Description

Second, the server receives the client's heart, but also need to respond to the client; the end of the service needs ServerHandlera little tinkering to the

    /**
     * 当收到数据的回调
     *
     * @param ctx  封装的连接对像
     * @param bean
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, PkgDataBean bean) throws Exception {
        switch (bean.getCmd()) {
            case 0x02:
                //响应客户端心跳
                bean.setCmd((byte) 0x03);
                ctx.channel().writeAndFlush(bean);
                break;
            default:
                break;
        }
        Log.d(TAG, "收到了解码器处理过的数据:" + bean.toString());
    }
  • We will receive the packet, the next modify the command byte is 0x03then sent back to the client

有了心跳机制就可以很好的判断连接是否是通畅,是否可以正常收发数据了

三、断线重连处理

当我们的网络不稳定,或者服务端一直未响应心跳、又或者切换网络导致连接中断;这个是就需要让它自动重连服务端,恢复链接

3.1 首先需要修改下NettyClient的连接代码,将NettyClient实例传递给ClientHandler

//...
private Bootstrap bootstrap;

public void connect() {
    try {
        NioEventLoopGroup group = new NioEventLoopGroup();
        bootstrap = new Bootstrap()
                // 指定channel类型
                .channel(NioSocketChannel.class)
                // 指定EventLoopGroup
                .group(group)
                // 指定Handler
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        pipeline.addLast(new IdleStateHandler(10, 0, 0));
                        //添加发送数据编码器
                        pipeline.addLast(new ClientEncoder());
                        //添加数据处理器
                        pipeline.addLast(new ClientHandler(NettyClient.this));
                    }
                });
        // 连接到服务端
        ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress(IP, PORT));
        //获取连接通道
        channel = channelFuture.sync().channel();
        handler.obtainMessage(0, "连接成功").sendToTarget();
    } catch (Exception e) {
        handler.obtainMessage(0, "连接失败").sendToTarget();
        Log.e(TAG, "连接失败:" + e.getMessage());
        e.printStackTrace();
    }
}

/**
 * 重连
 */
public void reConnect() {
    try {
        ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress(IP, PORT));
        channel = channelFuture.sync().channel();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

3.2 客户端与服务端连接断开了,那么会回调我们Handler中的channelInactive函数;所以只需要在这断开的回调启动重连任务就可以

public class ClientHandler extends SimpleChannelInboundHandler<Object> {

    private static final String TAG = "ClientHandler";
    private NettyClient client;

    public ClientHandler(NettyClient nettyClient) {
        this.client = nettyClient;

    }

    /**
     * 与服务端断开的回调
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        Log.d(TAG, "与服务端断开连接:" + ctx.toString());
        //启动重连
        reConnect(ctx);
    }

    /**
     * 5s重连一次服务端
     */
    private void reConnect(final ChannelHandlerContext ctx) {
        EventLoop loop = ctx.channel().eventLoop();
        loop.schedule(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "连接断开,发起重连");
                client.reConnect();
            }
        }, 5, TimeUnit.SECONDS);
    }
}

通过获得EventLoop,然后创建一个循环任务

3.3 当把客户端的网络断开然后在打开,然后发现了如下错误

Here Insert Picture Description
为什么抛了个错呢❌?原因当然是客户端访问不到服务端了,自然就抛了个错误;这样也就导致了刚才设置的5秒重连任务也就终止了。
那么这种情况要怎么处理呢?这种情况就需要用到ChannelFutureListener,监听连接状态

3.4 在NettyClient中为连接添加一个ChannelFutureListener监听连接状态,如下:

//...
private Bootstrap bootstrap;

public void connect() {
    try {
        NioEventLoopGroup group = new NioEventLoopGroup();
        bootstrap = new Bootstrap()
                // 指定channel类型
                .channel(NioSocketChannel.class)
                // 指定EventLoopGroup
                .group(group)
                // 指定Handler
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        pipeline.addLast(new IdleStateHandler(10, 0, 0));
                        //添加发送数据编码器
                        pipeline.addLast(new ClientEncoder());
                        //添加数据处理器
                        pipeline.addLast(new ClientHandler(NettyClient.this));
                    }
                });
        // 连接到服务端
        ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress(IP, PORT));
        // 添加连接状态监听
		channelFuture.addListener(new ConnectListener(this));
        //获取连接通道
        channel = channelFuture.sync().channel();
        handler.obtainMessage(0, "连接成功").sendToTarget();
    } catch (Exception e) {
        handler.obtainMessage(0, "连接失败").sendToTarget();
        Log.e(TAG, "连接失败:" + e.getMessage());
        e.printStackTrace();
    }
}

/**
 * 重连
 */
public void reConnect() {
    try {
        ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress(IP, PORT));
        // 添加连接状态监听
		channelFuture.addListener(new ConnectListener(this));
        channel = channelFuture.sync().channel();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

//添加连接状态监听
channelFuture.addListener(new ConnectListener(this));

  • ConnectListener.java实现ChannelFutureListener来获取连接状态
public class ConnectListener implements ChannelFutureListener {

    private static final String TAG = "ConnectListener";
    private NettyClient nettyClient;

    public ConnectListener(NettyClient nettyClient) {
        this.nettyClient = nettyClient;
    }

    @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() {
                    Log.d(TAG, "连接失败,发起重连");
                    nettyClient.reConnect();
                }
            }, 5, TimeUnit.SECONDS);
        }
    }

}
  • 现在我们再来看下运行效果
    Here Insert Picture Description
  • 可以看到程序一直在重连中达到了我们需要的效果,当再次打开网络的时候就可以连接成功了

四、到这里这篇文章的内容就说完了,下一篇将说下Netty的粘包和拆包这也是开发中经常遇见的问题

Demo将会在本系列第四篇文章中给出

Published 140 original articles · won praise 546 · views 540 000 +

Guess you like

Origin blog.csdn.net/a_zhon/article/details/100941312