Serie Netty: decodificador automático ReplayingDecoder en netty

¡Acostúmbrate a escribir juntos! Este es el sexto día de mi participación en el "Nuggets Daily New Plan · April Update Challenge", haz clic para ver los detalles del evento .

Introducción

Netty proporciona un decodificador de ByteBuf al mensaje definido por el usuario llamado ByteToMessageDecoder.Para usar este decodificador, debemos heredar este decodificador e implementar el método de decodificación, para implementar el contenido en ByteBuf al objeto de mensaje definido por el usuario en esta conversión de método .

Entonces, ¿qué problemas se encontrarán en el proceso de usar ByteToMessageDecoder? ¿Por qué hay otro ReplayingDecoder? Echemos un vistazo a esta pregunta.

Posibles problemas con ByteToMessageDecoder

Si desea implementar su propio decodificador para convertir ByteBuf en su propio objeto de mensaje, puede heredar ByteToMessageDecoder y luego implementar el método de decodificación. Primero veamos la definición del método de decodificación:

     protected void decode(ChannelHandlerContext ctx,
                             ByteBuf buf, List<Object> out) throws Exception 
复制代码

En los parámetros de entrada, buf es el ByteBuf a decodificar, y out es la lista de objetos decodificados.Necesitamos convertir los datos en ByteBuf en nuestro propio objeto y agregarlo a la lista de salida.

Entonces puede haber un problema aquí, porque los datos en buf pueden no estar listos cuando llamamos al método de decodificación.Por ejemplo, necesitamos un número entero, pero los datos en buf no son suficientes para un número entero, por lo que necesitamos algo de lógica de datos. en buf Judgement, describimos este proceso con un objeto Buf con longitud de mensaje.

El llamado objeto Buf con longitud de mensaje significa que los primeros 4 bits en el mensaje Buf constituyen un número entero, que representa la longitud del mensaje subsiguiente en el buf.

Por lo tanto, el proceso de lectura del mensaje para la conversión consiste en leer primero los primeros 4 bytes para obtener la longitud del mensaje y luego leer los bytes de esta longitud. Este es el contenido del mensaje que realmente queremos obtener.

Veamos cómo implementar esta lógica si se hereda de ByteToMessageDecoder.

   public class IntegerHeaderFrameDecoder extends ByteToMessageDecoder {
  
      @Override
     protected void decode(ChannelHandlerContext ctx,
                             ByteBuf buf, List<Object> out) throws Exception {
  
       if (buf.readableBytes() < 4) {
          return;
       }
  
       buf.markReaderIndex();
       int length = buf.readInt();
  
       if (buf.readableBytes() < length) {
          buf.resetReaderIndex();
          return;
       }
  
       out.add(buf.readBytes(length));
     }
   }
复制代码

在decode中,我们首先需要判断buf中可读的字节有没有4个,没有的话直接返回。如果有,则先读取这4个字节的长度,然后再判断buf中的可读字节是否小于应该读取的长度,如果小于,则说明数据还没有准备好,需要调用resetReaderIndex进行重置。

最后,如果所有的条件都满足,才真正进行读取工作。

有没有一个办法可以不提前进行判断,可以直接按照自己想要的内容来读取buf的方式呢?答案就是ReplayingDecoder。

我们先来看一下上面的例子用ReplayingDecoder重写是什么情况:

   public class IntegerHeaderFrameDecoder
        extends ReplayingDecoder<Void> {
  
     protected void decode(ChannelHandlerContext ctx,
                             ByteBuf buf, List<Object> out) throws Exception {
  
       out.add(buf.readBytes(buf.readInt()));
     }
   }
复制代码

使用ReplayingDecoder,我们可以忽略buf是否已经接收到了足够的可读数据,直接读取即可。

相比之下ReplayingDecoder非常的简单。接下来,我们来探究一下ReplayingDecoder的实现原理。

ReplayingDecoder的实现原理

ReplayingDecoder实际上是ByteToMessageDecoder的一个子类,它的定义如下:

public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder 
复制代码

在ByteToMessageDecoder中,最重要的方法是channelRead,在这个方法中实际调用了callDecode(ctx, cumulation, out);来实现cumulation到out的解码过程。

ReplayingDecoder的秘密就在于对这个方法的重写,我们来看下这个方法的具体实现:

   protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        replayable.setCumulation(in);
        try {
            while (in.isReadable()) {
                int oldReaderIndex = checkpoint = in.readerIndex();
                int outSize = out.size();
                if (outSize > 0) {
                    fireChannelRead(ctx, out, outSize);
                    out.clear();
                    if (ctx.isRemoved()) {
                        break;
                    }
                    outSize = 0;
                }
                S oldState = state;
                int oldInputLength = in.readableBytes();
                try {
                    decodeRemovalReentryProtection(ctx, replayable, out);
                    if (ctx.isRemoved()) {
                        break;
                    }
                    if (outSize == out.size()) {
                        if (oldInputLength == in.readableBytes() && oldState == state) {
                            throw new DecoderException(
                                    StringUtil.simpleClassName(getClass()) + ".decode() must consume the inbound " +
                                    "data or change its state if it did not decode anything.");
                        } else {
                            continue;
                        }
                    }
                } catch (Signal replay) {
                    replay.expect(REPLAY);
                    if (ctx.isRemoved()) {
                        break;
                    }

                    // Return to the checkpoint (or oldPosition) and retry.
                    int checkpoint = this.checkpoint;
                    if (checkpoint >= 0) {
                        in.readerIndex(checkpoint);
                    } else {
                    }
                    break;
                }
                if (oldReaderIndex == in.readerIndex() && oldState == state) {
                    throw new DecoderException(
                           StringUtil.simpleClassName(getClass()) + ".decode() method must consume the inbound data " +
                           "or change its state if it decoded something.");
                }
                if (isSingleDecode()) {
                    break;
                }
            }
        } catch (DecoderException e) {
            throw e;
        } catch (Exception cause) {
            throw new DecoderException(cause);
        }
    }
复制代码

这里的实现和ByteToMessageDecoder不同的是ReplayingDecoder中定义了一个checkpoint,这个checkpint是在尝试进行数据解码之初设置的:

int oldReaderIndex = checkpoint = in.readerIndex();
复制代码

如果是在解码的过程中出现了异常,则使用checkpoint重置index:

    int checkpoint = this.checkpoint;
         if (checkpoint >= 0) {
            in.readerIndex(checkpoint);
        } else {
    }
复制代码

这里捕获的异常是Signal,Signal是什么呢?

Signal是一个Error对象:

public final class Signal extends Error implements Constant<Signal> 
复制代码

这个异常是从replayable中抛出来的。

replayable是一个特有的ByteBuf对象,叫做ReplayingDecoderByteBuf:

final class ReplayingDecoderByteBuf extends ByteBuf
复制代码

在ReplayingDecoderByteBuf中定义了Signal属性:

    private static final Signal REPLAY = ReplayingDecoder.REPLAY;
复制代码

Esta excepción Signal se lanza desde el método get en ReplayingDecoderByteBuf. Tome getInt como ejemplo para ver cómo se lanza la excepción:

    public int getInt(int index) {
        checkIndex(index, 4);
        return buffer.getInt(index);
    }

复制代码

El método getInt primero llamará al método checkIndex para detectar la longitud en el buff.Si es menor que la longitud que se va a leer, se lanzará una excepción REPLAY:

    private void checkIndex(int index, int length) {
        if (index + length > buffer.writerIndex()) {
            throw REPLAY;
        }
    }
复制代码

Aquí es donde entra en juego la excepción Signal.

Resumir

Lo anterior es la introducción de ReplayingDecoder. Aunque ReplayingDecoder es fácil de usar, se puede ver a partir de su implementación que ReplayingDecoder reintenta continuamente lanzando excepciones, por lo que en algunos casos especiales, causará una degradación del rendimiento.

Es decir, al reducir la cantidad de nuestro código, se reduce la eficiencia de ejecución del programa. Parece que es imposible para un caballo correr y no pastar.

Este artículo ha sido publicado en www.flydean.com/14-4-netty-…

¡La interpretación más popular, los productos secos más profundos, los tutoriales más concisos y muchos trucos que no conoces están esperando que los descubras!

Bienvenido a prestar atención a mi cuenta oficial: "Programe esas cosas", comprenda la tecnología, ¡entiéndalo mejor!

Supongo que te gusta

Origin juejin.im/post/7083419504960602126
Recomendado
Clasificación