netty mecanismo de latido del corazón y reconexión

netty mecanismo de latido del corazón y reconexión

1. Teoría y descripción general

1. ¿Qué es un latido del corazón?

Como su nombre lo indica, el llamado  latido es un paquete de datos especial que se envía periódicamente entre el cliente y el servidor en una conexión TCP larga para notificar a la otra parte que todavía están en línea para garantizar la validez de la conexión TCP.

¿Por qué introducir el mecanismo de los latidos del corazón y su principio?

Debido a la falta de confiabilidad de la red, es posible que mientras TCP mantiene una conexión larga, algunas situaciones inesperadas, como cables de red desconectados, cortes repentinos de energía, etc., harán que se interrumpa la conexión entre el servidor y el cliente. En estas situaciones inesperadas A continuación, si no hay interacción entre el servidor y el cliente, entonces no pueden encontrar que la otra parte se ha desconectado en poco tiempo. Para resolver este problema, necesitamos introducir un   mecanismo de latido. . El principio de funcionamiento del mecanismo de latido es: en el servidor Cuando no hay interacción de datos con el cliente durante un cierto período de tiempo, es decir, en el estado inactivo, el cliente o servidor enviará un paquete de datos especial al otro parte. Cuando el receptor recibe el paquete de datos, también enviará inmediatamente un paquete de datos especial. El mensaje de datos responde al remitente , que es una interacción PING-PONG. Naturalmente, cuando un extremo recibe el mensaje de latido, sabe que el la otra parte todavía está en línea, lo que garantiza la validez de la conexión TCP

2. Netty se da cuenta del principio de los latidos del corazón.

Realizado por el procesador de IdleStateHandler:

Hay tres parámetros:

  • readerIdleTimeSeconds, tiempo de espera de lectura. Cuando no se leen datos en el canal dentro del tiempo especificado, se activará un evento IdleStateEvent de READER_IDLE.

  • WriterIdleTimeSeconds, tiempo de espera de escritura. Cuando no se escriben datos en el canal dentro del tiempo especificado, se activará un evento IdleStateEvent de WRITER_IDLE.

  • allIdleTimeSeconds, tiempo de espera de lectura / escritura. Es decir, cuando no hay ninguna operación de lectura o escritura dentro del intervalo de tiempo especificado, se activará un evento IdleStateEvent de ALL_IDLE.

Por ejemplo, configuramos en el cliente:

    p.addLast (new IdleStateHandler (0, 0, 5)); // Detección inactiva de lectura y escritura de 5 segundos.

¿Cómo recibe el procesador el estado inactivo después de agregar este IdleState? Nuestro procesador personalizado puede recibir el disparador del tiempo de estado inactivo en el método userEventTriggered .

    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent e = (IdleStateEvent) evt;
            switch (e.state()) {
                case READER_IDLE:  // 读空闲
                    handleReaderIdle(ctx);
                    break;
                case WRITER_IDLE: // 写空闲
                    handleWriterIdle(ctx);
                    break;
                case ALL_IDLE: // 读写空闲
                    handleAllIdle(ctx);
                    break;
                default:
                    break;
            }
        }
    }

3. El proceso de implementación del envío de latidos en esta demostración

En primer lugar, el cliente y el servidor son un programa de eco simple. El cliente configura IdleHandler para leer y escribir durante 5 segundos. El cliente envía mensajes al servidor en un bucle infinito, y el intervalo es de 0 a 20 segundos . Si el tiempo es mayor que 5 segundos Si no se envía información, se activará el evento idel (el cliente es el evento All). En este momento, el cliente envía un ping al servidor y el servidor devuelve pong al cliente después de recibir eso.

Entonces, ¿cómo realizar la reconexión del punto de ruptura en este artículo?

En primer lugar, por qué se habla tanto sobre la detección de latidos sobre la reconexión de puntos de interrupción, porque la reconexión de puntos de interrupción se basa en la detección de latidos para determinar si se ha perdido la conexión. A continuación, se describe el método de reconexión de puntos de interrupción y cómo la supervisión de los latidos juzga la pérdida de conexión. y desencadena la reconexión del punto de interrupción.

(1) La forma de volver a conectarse en el punto de interrupción:

Método de escucha (el método utilizado en esta demostración)

/**
	 * 连接的封装 (抽取连接过程方法,节省bootstrap创建的时间)
	 */
	public void doConnect() {
		if (channel != null && channel.isActive()) {
			return;
		}
		ChannelFuture future = bootstrap.connect(address, port);
		future.addListener(new ChannelFutureListener() {
			public void operationComplete(ChannelFuture futureListener) throws Exception {
				if (futureListener.isSuccess()) {
					channel = futureListener.channel();
					System.out.println("Connect to server successfully!");
				} else {
					System.out.println("Failed to connect to server, try connect after 10s");

					futureListener.channel().eventLoop().schedule(new Runnable() {
						@Override
						public void run() {
							// 重连
							doConnect();
						}
					}, 1, TimeUnit.SECONDS);
				}
			}
		});
	}

Vuelva a conectar en inactivo

 

public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		System.out.println("断开链接重连" + ctx.channel().localAddress().toString());
		new Thread(new Runnable() {
			@Override
			public void run() {
			    // 重连代码
			}
		}).start();
	}

2) Juicio y reconexión de gatillo

Debido a que la configuración del servidor anterior es leer 10 segundos para activar el inactivo, y el cliente se configura para leer y escribir inactivo en 5 segundos. Si el servidor no ha leído ningún dato en 10 segundos, entonces el cliente no ha hecho ping dos veces, esta vez Preliminarmente juzgue que la conexión del cliente se perdió, ejecute ChannelHandlerContext ctx.close (), y el cliente puede activar una reconexión. (Esta demostración no ha hecho ninguna excepción que active la reconexión por el momento; si desea hacerlo, solo necesita hacer que el ping del cliente no se envíe siempre)

4. Formato de mensaje complementario

[longitud, tipo, datos] // El tipo 1 es ping, 2 es pong y 3 son otros mensajes.

Este mensaje puede usar LengthFieldBasedFrameDecoder para procesar paquetes adhesivos de semi-paquetes.

 

Dos, código de demostración

1. Procesador de propósito general

public abstract class CustomHeartbeatHandler extends SimpleChannelInboundHandler<ByteBuf> {
  
    protected String name;
    private int heartbeatCount = 0;

    public CustomHeartbeatHandler(String name) {
        this.name = name;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext context, ByteBuf byteBuf) throws Exception {
    	// 第4角标是否是ping --服务端接收到ping,需要发送pong
        if (byteBuf.getByte(4) == Consts.PING_MSG) {
            sendPongMsg(context);
        // 客户端可以接收pong
        } else if (byteBuf.getByte(4) == Consts.PONG_MSG){
            System.out.println(name + " get pong msg from " + context.channel().remoteAddress());
        } else {
            handleData(context, byteBuf);
        }
    }

    protected void sendPingMsg(ChannelHandlerContext context) {
        ByteBuf buf = context.alloc().buffer(5);
        buf.writeInt(5);
        buf.writeByte(Consts.PING_MSG);
        context.writeAndFlush(buf);
        heartbeatCount++;
        System.out.println(name + " sent ping msg to " + context.channel().remoteAddress() + ", count: " + heartbeatCount);
    }

    private void sendPongMsg(ChannelHandlerContext context) {
        ByteBuf buf = context.alloc().buffer(5);
        buf.writeInt(5);
        buf.writeByte(Consts.PONG_MSG);
        context.channel().writeAndFlush(buf);
        heartbeatCount++;
        System.out.println(name + " sent pong msg to " + context.channel().remoteAddress() + ", count: " + heartbeatCount);
    }

    protected abstract void handleData(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf);

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent e = (IdleStateEvent) evt;
            switch (e.state()) {
                case READER_IDLE:  // 读空闲
                    handleReaderIdle(ctx);
                    break;
                case WRITER_IDLE: // 写空闲
                    handleWriterIdle(ctx);
                    break;
                case ALL_IDLE: // 读写空闲
                    handleAllIdle(ctx);
                    break;
                default:
                    break;
            }
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.err.println("---" + ctx.channel().remoteAddress() + " is active---");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.err.println("---" + ctx.channel().remoteAddress() + " is inactive---");
    }

    protected void handleReaderIdle(ChannelHandlerContext ctx) {
        System.err.println("---READER_IDLE---");
    }

    protected void handleWriterIdle(ChannelHandlerContext ctx) {
        System.err.println("---WRITER_IDLE---");
    }

    protected void handleAllIdle(ChannelHandlerContext ctx) {
        System.err.println("---ALL_IDLE---");
    }
}

2. Cliente

package netty.ping_pong.client;

import java.util.Random;
import java.util.concurrent.TimeUnit;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.timeout.IdleStateHandler;
import netty.ping_pong.domain.Consts;

public class PingClient {
	
	private String address;
	private int port;
	private NioEventLoopGroup workGroup = new NioEventLoopGroup(4);
	private Channel channel;
	private Bootstrap bootstrap;
	
	public PingClient(String address, int port) {
		super();
		this.address = address;
		this.port = port;
	}

	public static void main(String[] args) {
		PingClient client = new PingClient("127.0.0.1", 7000);
		client.start();
	}
	
	/**
	 * 启动
	 */
	public void start() {
		try {
			// 创建
			bootstrap = new Bootstrap();
			bootstrap.group(workGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
				protected void initChannel(SocketChannel socketChannel) throws Exception {
					ChannelPipeline p = socketChannel.pipeline();
					p.addLast(new IdleStateHandler(0, 0, 5)); // 5 秒读写idle检测
					p.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, -4, 0));
					p.addLast(new PingHandler());
				}
			});
			
			// 连接和监听重连 
			doConnect();
			
			// 发送数据 -- 间隔时间是0-20秒随机
			Random random = new Random();
			for (int i = 0; i < 10000; i++) {
				if (channel != null && channel.isActive()) {
					String content = "client msg " + i;
					ByteBuf buf = channel.alloc().buffer(5 + content.getBytes().length);
					buf.writeInt(5 + content.getBytes().length);
					buf.writeByte(Consts.CUSTOM_MSG);
					buf.writeBytes(content.getBytes());
					channel.writeAndFlush(buf);
				}
				Thread.sleep(random.nextInt(20000));
			}
		} catch (Exception e) {
            throw new RuntimeException(e);
        }
	}

	
	/**
	 * 连接的封装 (抽取连接过程方法,节省bootstrap创建的时间)
	 */
	public void doConnect() {
		if (channel != null && channel.isActive()) {
			return;
		}
		ChannelFuture future = bootstrap.connect(address, port);
		future.addListener(new ChannelFutureListener() {
			public void operationComplete(ChannelFuture futureListener) throws Exception {
				if (futureListener.isSuccess()) {
					channel = futureListener.channel();
					System.out.println("Connect to server successfully!");
				} else {
					System.out.println("Failed to connect to server, try connect after 10s");

					futureListener.channel().eventLoop().schedule(new Runnable() {
						@Override
						public void run() {
							// 重连
							doConnect();
						}
					}, 10, TimeUnit.SECONDS);
				}
			}
		});
	}
	
	public void close() {
		if(channel!=null) {
			channel.close();
		}
		workGroup.shutdownGracefully();
	}

}
package netty.ping_pong.client;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import netty.ping_pong.server.common.CustomHeartbeatHandler;

public class PingHandler extends CustomHeartbeatHandler{

	public PingHandler() {
		super("client");
	}

	/**正常数据的处理**/
	@Override
	protected void handleData(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) {
		byte[] data = new byte[byteBuf.readableBytes() - 5];
		byteBuf.skipBytes(5);
		byteBuf.readBytes(data);
		String content = new String(data);
		System.out.println(name + " get content: " + content);
	}
	
	/**空闲all触发的时候,进行发送ping数据**/ 
	@Override
	protected void handleAllIdle(ChannelHandlerContext ctx) {
		super.handleAllIdle(ctx);
		sendPingMsg(ctx);
	}

}

3. Servidor

package netty.ping_pong.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;

public class PingServer {

	public static void main(String[] args) {
		PingServer server = new PingServer();
		server.bind(7000);
	}

	public void bind(int port) {
		EventLoopGroup bossGroup = new NioEventLoopGroup(1);
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			ServerBootstrap b = new ServerBootstrap();
			b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100)
					.handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {
						@Override
						protected void initChannel(SocketChannel ch) throws Exception {
							ch.pipeline().addLast(new IdleStateHandler(10, 0, 0));
							// lengthAdjustment: 总长-长度字段-长度字段描述 = -4表示长度字段描述就是总长
							ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, -4, 0));
							ch.pipeline().addLast(new PingHandler());
						}
					});

			ChannelFuture future = b.bind(port).sync();
			System.out.println("server start now");
			future.channel().closeFuture().sync();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
		}
	}

}
package netty.ping_pong.server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import netty.ping_pong.server.common.CustomHeartbeatHandler;

public class PingHandler extends CustomHeartbeatHandler{

	public PingHandler() {
		super("server");
	}

	@Override
	protected void handleData(ChannelHandlerContext channelHandlerContext, ByteBuf buf) {
		byte[] data = new byte[buf.readableBytes() - 5];
		ByteBuf responseBuf = Unpooled.copiedBuffer(buf);
		buf.skipBytes(5);
		buf.readBytes(data);
		String content = new String(data);
		System.out.println(name + " get content: " + content);
		channelHandlerContext.write(responseBuf);
	}

	// 服务端10秒没有读取到数据 (超时),关闭客户端连接。
	@Override
	protected void handleReaderIdle(ChannelHandlerContext ctx) {
		super.handleReaderIdle(ctx);
		System.err.println("---client " + ctx.channel().remoteAddress().toString() + " reader timeout, close it---");
		ctx.close();
	}
	
}

 

 

Supongo que te gusta

Origin blog.csdn.net/shuixiou1/article/details/115058019
Recomendado
Clasificación