netty的心跳检测与断线重连

服务端代码

NettyServer
package com.example.netty.netty2;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class NettyServer {

    private NettyServerChannelInitializer serverChannelInitializer = null;

    private int port = 8888;


    public void bind() throws Exception {
        //配置服务端的NIO线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            serverChannelInitializer = new NettyServerChannelInitializer();
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    //保持长连接
                    .childOption(ChannelOption.SO_KEEPALIVE,true)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(serverChannelInitializer);

            //绑定端口,同步等待成功
            ChannelFuture f = b.bind(port).sync();


            //等待服务器监听端口关闭
            f.channel().closeFuture().sync();
        } finally {
            //释放线程池资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        new  NettyServer().bind();
    }

}

自定义channel类  里面涉及有拆包,粘包,编解码以及其他自定义操作(心跳检测)

package com.example.netty.netty2;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.concurrent.TimeUnit;


public class NettyServerChannelInitializer extends ChannelInitializer {

    private NettyServerHandler handler ;



    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        //解决TCP粘包拆包的问题,以特定的字符结尾($_)
        pipeline.addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, Unpooled.copiedBuffer("$_".getBytes())));
        //字符串解码和编码
        pipeline.addLast("decoder", new StringDecoder());
        pipeline.addLast("encoder", new StringEncoder());
        pipeline.addLast(new IdleStateHandler(3,0,0,TimeUnit.SECONDS));
        //服务器的逻辑
        handler = new NettyServerHandler();
        pipeline.addLast("handler", handler);
    }
}
NettyServerHandler 心跳检测
package com.example.netty.netty2;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;


@Slf4j
public class NettyServerHandler extends SimpleChannelInboundHandler {

    /**
     * 心跳丢失次数
     */
    private int counter = 0;


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("Client say : " + msg.toString());
        //重置心跳丢失次数
        counter = 0;
    }


    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("服务端被 : " + ctx.channel().remoteAddress().toString()+ " 连接上了 !");
        super.channelActive(ctx);
    }


    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state().equals(IdleState.READER_IDLE)){
                // 空闲40s之后触发 (心跳包丢失)
                if (counter >= 3) {
                    // 连续丢失3个心跳包 (断开连接)
                    ctx.channel().close().sync();
                    log.error("已与"+ctx.channel().remoteAddress()+"断开连接");
                    System.out.println("已与"+ctx.channel().remoteAddress()+"断开连接");
                } else {
                    counter++;
                    log.debug(ctx.channel().remoteAddress() + "丢失了第 "+ctx.channel().remoteAddress() + counter + " 个心跳包");
                    System.out.println("丢失了"+ctx.channel().remoteAddress()+"第 " + counter + " 个心跳包");
                }
            }

        }
    }
}

客户端

NettyClient
package com.example.netty.netty2;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;

@Slf4j
public class NettyClient {

    private String host;

    private int port;

    private static Channel channel;

    public NettyClient(){

    }

    public NettyClient(String host, int port) {
        this.host = host;
        this.port = port;
    }



    public void start()  {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .option(ChannelOption.SO_KEEPALIVE,true)
                    .channel(NioSocketChannel.class)
                    .handler(new ClientChannelInitializer(host,port));

            ChannelFuture f = b.connect(host,port);

//            //断线重连
            f.addListener(new ChannelFutureListener() {
                @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.error("服务端链接不上,开始重连操作...");
                                System.err.println("服务端链接不上,开始重连操作...");
                                start();
                            }
                        }, 1L, TimeUnit.SECONDS);
                    } else {
                        channel = channelFuture.channel();
                        log.info("服务端链接成功...");
                        System.err.println("服务端链接成功...");
                    }
                }
            });

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new NettyClient ("127.0.0.1",8888).start();
    }
}
ClientChannelInitializer
package com.example.netty.netty2;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.concurrent.TimeUnit;

public class ClientChannelInitializer extends ChannelInitializer {

    private String host;
    private int port;

    public ClientChannelInitializer( String host, int port) {
        this.host = host;
        this.port = port;
    }

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        //解决TCP粘包拆包的问题,以特定的字符结尾($_)
        pipeline.addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, Unpooled.copiedBuffer("$_".getBytes())));
        //字符串解码和编码
        pipeline.addLast("decoder", new StringDecoder());
        pipeline.addLast("encoder", new StringEncoder());
        //心跳检测
        pipeline.addLast(new IdleStateHandler(0,10,0,TimeUnit.SECONDS));
        //客户端的逻辑
        pipeline.addLast("handler", new NettyClientHandler(host,port));

    }
}
NettyClientHandler
package com.example.netty.netty2;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoop;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class NettyClientHandler extends SimpleChannelInboundHandler {

    private String host;
    private int port;
    private NettyClient nettyClinet;
    private String tenantId;


    public NettyClientHandler(String host, int port) {
        this.host = host;
        this.port = port;
        nettyClinet = new NettyClient(host,port);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o)
            throws Exception {
        System.out.println("Server say : " + o.toString());
    }


    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("通道已连接!!");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("断线了。。。。。。");
        //使用过程中断线重连
        final EventLoop eventLoop = ctx.channel().eventLoop();
        eventLoop.schedule(new Runnable() {
            @Override
            public void run() {
                nettyClinet.start();
            }
        }, 1, TimeUnit.SECONDS);

        ctx.fireChannelInactive();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
            throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state().equals(IdleState.READER_IDLE)) {
                System.out.println("READER_IDLE");

            } else if (event.state().equals(IdleState.WRITER_IDLE)) {
                /**发送心跳,保持长连接*/
                String  s = "ping$_";
                ctx.channel().writeAndFlush(s);
                log.debug("心跳发送成功!");
                System.out.println("心跳发送成功!");
            } else if (event.state().equals(IdleState.ALL_IDLE)) {
                System.out.println("ALL_IDLE");
            }
        }
        super.userEventTriggered(ctx, evt);
    }

}

心跳检测类里面关键代码IdleStateHandler

readerIdleTime:为读超时时间(即测试端一定时间内未接受到被测试端消息)
writerIdleTime:为写超时时间(即测试端一定时间内向被测试端发送消息)
allIdleTime:所有类型的超时时间
比如在这里,服务端每隔3S检测一下,若超过3次,就自动断开连接,
客户端是每个10S中发送一个心跳包到服务端。
so,如非异常情况,会一直保持通信,因为服务端在将要端口连接的时候,这个时候客户端发送了一个心跳包。

你可能感兴趣的:(java)