inicialización del cliente de código fuente netty

Ejemplo de código de cliente de Netty:

public Test connect(int port, String host, final String nickName) {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
​
                }
            });
            //发起同步连接操作
            ChannelFuture channelFuture = bootstrap
                    .connect(host, port)
                    .sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //关闭,释放线程资源
            group.shutdownGracefully();
        }
        return this;
    }
​
    public static void main(String[] args) {
        new Test().connect(8080, "localhost", "Tom");
    }

Establezca NioSocketChannel.class en el canal, se llamará al siguiente método de AbstractBootstrap

public B channel(Class<? extends C> channelClass) {
        return channelFactory(new ReflectiveChannelFactory<C>(
                ObjectUtil.checkNotNull(channelClass, "channelClass")
        ));
    }

ReflectiveChannelFactory implementa la interfaz ChannelFactory, que proporciona el único método, a saber, el método newChannel (). ChannelFactory, como su nombre lo indica, es la clase de fábrica para crear Channel. Ingresando al método newChannel () de ReflectiveChannelFactory, vemos que su código de implementación es el siguiente:

public ReflectiveChannelFactory(Class<? extends T> clazz) {
    ObjectUtil.checkNotNull(clazz, "clazz");
    try {
        this.constructor = clazz.getConstructor();
    } catch (NoSuchMethodException e) {
        throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
                                           " does not have a public non-arg constructor", e);
    }
}
​
@Override
public T newChannel() {
    try {
        return constructor.newInstance();
    } catch (Throwable t) {
        throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
    }
}

Según el código anterior:

1. La clase de implementación ChannelFactory en Bootstrap es ReflectiveChannelFactory.

2. El tipo específico de canal creado por el método channel () es NioSocketChannel.

El proceso de creación de instancias de canal es en realidad llamar al método newChannel () de ChannelFactory, y el tipo específico de canal instanciado está relacionado con los parámetros del método channel () que se pasa cuando se inicializa Bootstrap. Entonces, para el cliente Bootstrap, la instancia de canal creada es NioSocketChannel

Inicialización del canal de cliente

Ya supimos cómo establecer el tipo de un canal, y aprendimos que Channel es instanciado por el método newChannel () de ChannelFactory, entonces, ¿dónde se llama el método newChannel () de ChannelFactory? Continúe rastreando, encontramos que su cadena de llamadas es la siguiente :

En initAndRegister () de AbstractBootstrap, se llama a newChannel () de ChannelFactory () para crear una instancia de NioSocketChannel. El código fuente es el siguiente:

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            //调用的是channelFactory接口的实现类ReflectiveChannelFactory中的newChannel
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                // channel can be null if newChannel crashed (eg SocketException("too many open files"))
                channel.unsafe().closeForcibly();
                // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }
​
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }

En el método newChannel (), use el mecanismo de reflexión para llamar al método newInstance () del objeto de clase para crear una nueva instancia de Channel, que es equivalente a llamar al constructor predeterminado de NioSocketChannel. El código de constructor predeterminado de NioSocketChannel es el siguiente:

public NioSocketChannel() {
        this(DEFAULT_SELECTOR_PROVIDER);
 }
​
public NioSocketChannel(SelectorProvider provider) {
        this(newSocket(provider));
 }
​
 private static SocketChannel newSocket(SelectorProvider provider) {
        try {
            /**
             *  Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
             *  {@link SelectorProvider#provider()} which is called by each SocketChannel.open() otherwise.
             *
             *  See <a href="https://github.com/netty/netty/issues/2308">#2308</a>.
             */
            return provider.openSocketChannel();
        } catch (IOException e) {
            throw new ChannelException("Failed to open a socket.", e);
        }
 }
​
 

El código aquí es más crítico. Vemos que en este constructor, se llama a newSocket () para abrir un nuevo Java NIO SocketChannel:

Luego, la clase padre, el constructor de AbstractNioByteChannel se llamará:

public NioSocketChannel(SocketChannel socket) {
        this(null, socket);
 }
​
public NioSocketChannel(Channel parent, SocketChannel socket) {
        super(parent, socket);
        config = new NioSocketChannelConfig(this, socket.socket());
    }

Y el padre del parámetro entrante es nulo, ch es el objeto Java NIO SocketChannel creado al llamar a newSocket () justo ahora, por lo que el recién creado

El padre del objeto NioSocketChannel es temporalmente nulo.

 protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, SelectionKey.OP_READ);
  }

Luego continuará llamando al constructor de la clase padre AbstractNioChannel y pasará el parámetro real readInterestOp = SelectionKey.OP_READ:

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            ch.configureBlocking(false);
        } catch (IOException e) {
            try {
                ch.close();
            } catch (IOException e2) {
                logger.warn(
                            "Failed to close a partially initialized socket.", e2);
            }
​
            throw new ChannelException("Failed to enter non-blocking mode.", e);
        }
  }

Luego continúe llamando al constructor de la clase padre AbstractChannel:

protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }

En este punto, la inicialización de NioSocketChannel está completa y podemos resumir brevemente el trabajo realizado por la inicialización de NioSocketChannel:

1. Llame a NioSocketChannel.newSocket (DEFAULT_SELECTOR_PROVIDER) para abrir un nuevo NIOSocketChannel de Java.

2. Atributos que deben inicializarse en AbstractChannel (canal principal):

id: cada canal tiene un id único.

padre: el atributo se establece en nulo.

inseguro: crea una instancia de un objeto inseguro a través de newUnsafe (), su tipo es la clase interna AbstractNioByteChannel.NioByteUnsafe.

pipeline: es una instancia recién creada de la nueva DefaultChannelPipeline (this).

3. Propiedades en AbstractNioChannel:

ch: Asignado a Java SocketChannel, que es el Java NIO SocketChannel devuelto por el método newSocket () de NioSocketChannel.

readInterestOp: asignado a SelectionKey.OP_READ

ch: está configurado como sin bloqueo, es decir, llama a ch.configureBlocking (falso)

4. Propiedades en NioSocketChannel:

config = new NioSocketChannelConfig (esto, socket.socket ())

Inicialización del campo Inseguro

Mencionamos brevemente que en el proceso de instanciar NioSocketChannel, se llamará en el constructor de la clase padre AbstractChannel

newUnsafe () para obtener una instancia insegura. Entonces, ¿cómo se inicializa inseguro ?, ¿cuál es su función?

De hecho, inseguro es particularmente crítico ya que encapsula el funcionamiento del Socket inferior de Java, por lo que en realidad es un puente importante entre la capa superior de Netty y la capa inferior de Java. Así que echemos un vistazo a los métodos proporcionados por la interfaz Insegura:

interface Unsafe {
    RecvByteBufAllocator.Handle recvBufAllocHandle();

    SocketAddress localAddress();

    SocketAddress remoteAddress();

    void register(EventLoop eventLoop, ChannelPromise promise);

    void bind(SocketAddress localAddress, ChannelPromise promise);

    void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);

    void disconnect(ChannelPromise promise);

    void close(ChannelPromise promise);

    void closeForcibly();

    void deregister(ChannelPromise promise);

    void beginRead();

    void write(Object msg, ChannelPromise promise);

    void flush();

    ChannelPromise voidPromise();

    ChannelOutboundBuffer outboundBuffer();
} 

Puede verse en el código fuente que estos métodos en realidad corresponden a la operación del Java Socket subyacente. Volviendo al método de construcción de AbstractChannel, aquí se llama a newUnsafe () para obtener un nuevo objeto inseguro, y el método newUnsafe () se anula en NioSocketChannel. Mira el codigo

@Override
protected AbstractNioUnsafe newUnsafe() {
	return new NioSocketChannelUnsafe();
}

El método newUnsafe () de NioSocketChannel devolverá una instancia de NioSocketChannelUnsafe. Desde aquí, podemos confirmar que el campo inseguro en el NioSocketChannel instanciado es en realidad una instancia de NioSocketChannelUnsafe.

Inicialización de Pipeline

Arriba analizamos el proceso de inicialización general de NioSocketChannel, pero nos perdimos una parte clave, a saber, la inicialización de ChannelPipeline. En las notas del Pipeline, dice "Cada canal tiene su propio pipeline y se crea automáticamente cuando se crea un nuevo canal", sabemos que al crear una instancia de un canal, se debe crear una instancia de ChannelPipeline. Y vimos que el campo de canalización se inicializó en una instancia de DefaultChannelPipeline en el constructor de AbstractChannel. Así que echemos un vistazo a lo que hace el constructor DefaultChannelPipeline

 protected DefaultChannelPipeline(Channel channel) {
        this.channel = ObjectUtil.checkNotNull(channel, "channel");
        succeededFuture = new SucceededChannelFuture(channel, null);
        voidPromise =  new VoidChannelPromise(channel, true);

        tail = new TailContext(this);
        head = new HeadContext(this);

        head.next = tail;
        tail.prev = head;
 }

El constructor de DefaultChannelPipeline necesita pasar en un canal, y este canal es en realidad el NioSocketChannel que instanciamos, y DefaultChannelPipeline almacenará este objeto NioSocketChannel en el campo del canal. También hay dos campos especiales en DefaultChannelPipeline, a saber, cabeza y cola, que son la cabeza y la cola de la lista doblemente enlazada. De hecho, en DefaultChannelPipeline, se mantiene una lista doblemente enlazada con AbstractChannelHandlerContext como elemento de nodo Esta lista enlazada es la clave para la implementación de Netty del mecanismo Pipeline.

En la lista vinculada, el encabezado es ChannelOutboundHandler y el final es ChannelInboundHandler. Luego mire el constructor HeadContext:

HeadContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, HEAD_NAME, HeadContext.class);
            unsafe = pipeline.channel().unsafe();
            setAddComplete();
}

 

Inicialización de EventLoop

Volviendo al código de usuario de ChatClient inicial, creamos una instancia de un objeto NioEventLoopGroup desde el principio, por lo que rastrearemos el proceso de inicialización de EventLoop desde su constructor.

NioEventLoop tiene varios constructores sobrecargados, pero el contenido no es muy diferente. Al final, todos se llaman constructores de la clase padre MultithreadEventLoopGroup:

protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}

Una cosa interesante es que si el número de subprocesos que pasamos en nThreads es 0, entonces Netty establecerá el número predeterminado de subprocesos para nosotros.

DEFAULT_EVENT_LOOP_THREADS, y ¿cómo se determina el número predeterminado de subprocesos?

De hecho, es muy simple. En el bloque de código estático, el valor de DEFAULT_EVENT_LOOP_THREADS se determinará primero:

static {
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

        if (logger.isDebugEnabled()) {
            logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
        }
    }

Netty obtendrá primero el valor de "io.netty.eventLoopThreads" de las propiedades del sistema. Si no lo configuramos, volverá al valor predeterminado: el número de núcleos de procesador * 2. De vuelta al constructor MultithreadEventLoopGroup continuará llamando al constructor de la clase padre MultithreadEventExecutorGroup:

 protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
        if (nThreads <= 0) {
            throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
        }

        if (executor == null) {
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }
        //*
        children = new EventExecutor[nThreads];

        for (int i = 0; i < nThreads; i ++) {
            boolean success = false;
            try {
                //上面是异常处理代码
                //*
                children[i] = newChild(executor, args);
                success = true;
            } catch (Exception e) {
                // TODO: Think about if this is a good exception type
                throw new IllegalStateException("failed to create a child event loop", e);
            } finally {
                if (!success) {
                    for (int j = 0; j < i; j ++) {
                        children[j].shutdownGracefully();
                    }

                    for (int j = 0; j < i; j ++) {
                        EventExecutor e = children[j];
                        try {
                            while (!e.isTerminated()) {
                                e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException interrupted) {
                            // Let the caller handle the interruption.
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }
        }
        //*
        chooser = chooserFactory.newChooser(children);
        //包装处理代码
        final FutureListener<Object> terminationListener = new FutureListener<Object>() {
            @Override
            public void operationComplete(Future<Object> future) throws Exception {
                if (terminatedChildren.incrementAndGet() == children.length) {
                    terminationFuture.setSuccess(null);
                }
            }
        };

        for (EventExecutor e: children) {
            e.terminationFuture().addListener(terminationListener);
        }

        Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
        Collections.addAll(childrenSet, children);
        readonlyChildren = Collections.unmodifiableSet(childrenSet);
    }
@Override
    public EventExecutorChooser newChooser(EventExecutor[] executors) {
        if (isPowerOfTwo(executors.length)) {
            return new PowerOfTwoEventExecutorChooser(executors);
        } else {
            return new GenericEventExecutorChooser(executors);
        }
    }

    private static boolean isPowerOfTwo(int val) {
        return (val & -val) == val;
    }

    private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
        private final AtomicInteger idx = new AtomicInteger();
        private final EventExecutor[] executors;

        PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
            this.executors = executors;
        }

        @Override
        public EventExecutor next() {
            return executors[idx.getAndIncrement() & executors.length - 1];
        }
    }

    private static final class GenericEventExecutorChooser implements EventExecutorChooser {
        // Use a 'long' counter to avoid non-round-robin behaviour at the 32-bit overflow boundary.
        // The 64-bit long solves this by placing the overflow so far into the future, that no system
        // will encounter this in practice.
        private final AtomicLong idx = new AtomicLong();
        private final EventExecutor[] executors;

        GenericEventExecutorChooser(EventExecutor[] executors) {
            this.executors = executors;
        }

        @Override
        public EventExecutor next() {
            return executors[(int) Math.abs(idx.getAndIncrement() % executors.length)];
        }
    }

El significado principal de la lógica del código anterior es: es decir, si nThreads es una potencia de 2, use PowerOfTwoEventExecutorChooser; de lo contrario, use GenericEventExecutorChooser. Ambos Selectores anulan el método next (). La función principal del método next () es cambiar cíclicamente el índice de la matriz, como se muestra en la siguiente figura:

La lógica de esta operación es realmente muy simple, es decir, cada vez que se incrementa el índice y la longitud de la matriz es módulo: idx.getAndIncrement ()% ejecutores.length. Pero incluso una operación de índice de matriz muy simple, Netty nos ha ayudado a optimizarla. Porque en la parte inferior de la computadora, & y% son más eficientes.

Bueno, deberíamos haber tenido muy claro la lógica de procesamiento en MultithreadEventExecutorGroup analizándola, por lo que podemos resumir brevemente:

1. Cree una matriz SingleThreadEventExecutor de tamaño nThreads.

2. Cree diferentes Selectores de acuerdo con el tamaño de nThreads, es decir, si nThreads es una potencia de 2, use

PowerOfTwoEventExecutorChooser; de lo contrario, utilice GenericEventExecutorChooser. No importa qué Selector se use, sus funciones son las mismas, es decir, se selecciona una instancia de EventExecutor adecuada de la matriz secundaria.

3. Llame al método newChhild () para inicializar la matriz secundaria.

De acuerdo con el código anterior, también podemos saber: MultithreadEventExecutorGroup mantiene una matriz EventExecutor internamente, y el mecanismo de implementación EventLoopGroup de Netty está construido en MultithreadEventExecutorGroup. Siempre que Netty necesite un EventLoop, llamará al método next () para obtener un EventLoop disponible. La última parte del código anterior es el método newChild (), que es un método abstracto cuya tarea es crear una instancia del objeto EventLoop. Rastreemos su código. se puede descubrir. Este método está implementado en la clase NioEventLoopGroup, y su contenido es muy simple:

@Override
    protected EventLoop newChild(Executor executor, Object... args) throws Exception {
        EventLoopTaskQueueFactory queueFactory = args.length == 4 ? (EventLoopTaskQueueFactory) args[3] : null;
        return new NioEventLoop(this, executor, (SelectorProvider) args[0],
            ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2], queueFactory);
    }

De hecho, la lógica es muy simple: cree una instancia de un objeto NioEventLoop y luego devuelva el objeto NioEventLoop. Finalmente, resuma el proceso de inicialización de todo el EventLoopGroup:

1. EventLoopGroup (en realidad, MultithreadEventExecutorGroup) mantiene internamente una matriz de niños de tipo EventExecutor,

Su tamaño es nThreads, que constituye un grupo de subprocesos.

2. Si especificamos el tamaño del grupo de subprocesos al crear una instancia de NioEventLoopGroup, entonces nThreads es el valor especificado y viceversa.

Número de núcleos de procesador * 2.

3. El método abstracto newChild () se llama en MultithreadEventExecutorGroup para inicializar la matriz secundaria.

4. El método abstracto newChild () se implementa en NioEventLoopGroup y devuelve una instancia de NioEventLoop.

5. Asignación de propiedad de NioEventLoop:

proveedor: obtenga un SelectorProvider a través de SelectorProvider.provider () en el constructor NioEventLoopGroup.

selector: en el constructor NioEventLoop, obtenga un objeto selector llamando al método provider.openSelector ().

Canal registrado en Selector

En el análisis anterior, mencionamos que Channel se inicializará en initAndRegister () de Bootstrap, pero este método también registrará el Channe inicializado en el selector de NioEventLoop. A continuación, analicemos el proceso de registro de canales.

Recuerde el método initAndRegister () de AbstractBootstrap:

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                // channel can be null if newChannel crashed (eg SocketException("too many open files"))
                channel.unsafe().closeForcibly();
                // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }

        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }

Cuando se inicializa el canal, se llamará al método group (). Register () para registrar el canal con el selector. Si continuamos con el seguimiento, lo haremos

Se encuentra que la cadena de llamadas es la siguiente:

 

¿Qué se hace exactamente en el método AbstractChannel $ AbstractUnsafe.register ()?

@Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            ObjectUtil.checkNotNull(eventLoop, "eventLoop");
            if (isRegistered()) {
                promise.setFailure(new IllegalStateException("registered to an event loop already"));
                return;
            }
            if (!isCompatible(eventLoop)) {
                promise.setFailure(
                        new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
                return;
            }

            AbstractChannel.this.eventLoop = eventLoop;

            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                    logger.warn(
                            "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                            AbstractChannel.this, t);
                    closeForcibly();
                    closeFuture.setClosed();
                    safeSetFailure(promise, t);
                }
            }
        }

Primero, asigne eventLoop a la propiedad eventLoop de Channel, y sabemos que este objeto eventLoop es en realidad

Obtenido por el método next () de MultithreadEventLoopGroup. Según nuestro análisis anterior, podemos determinar que el objeto eventLoop devuelto por el método next () es una instancia de NioEventLoop. El método register () luego llama al método register0 ():

private void register0(ChannelPromise promise) {
            try {
                // check if the channel is still open as it could be closed in the mean time when the register
                // call was outside of the eventLoop
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;
                doRegister();
                neverRegistered = false;
                registered = true;

                // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
                // user may already fire events through the pipeline in the ChannelFutureListener.
                pipeline.invokeHandlerAddedIfNeeded();

                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();
                // Only fire a channelActive if the channel has never been registered. This prevents firing
                // multiple channel actives if the channel is deregistered and re-registered.
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        // This channel was registered before and autoRead() is set. This means we need to begin read
                        // again so that we process inbound data.
                        //
                        // See https://github.com/netty/netty/issues/4805
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                // Close the channel directly to avoid FD leak.
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }

El método register0 () llama al método doRegister () de AbstractNioChannel

@Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    // Force the Selector to select now as the "canceled" SelectionKey may still be
                    // cached and not removed because no Select.select(..) operation was called yet.
                    eventLoop().selectNow();
                    selected = true;
                } else {
                    // We forced a select operation on the selector before but the SelectionKey is still cached
                    // for whatever reason. JDK bug ?
                    throw e;
                }
            }
        }
    }

Vea que el método javaChannel () ya se conocía antes. Devuelve un objeto Java NIO SocketChannel. Aquí

Registramos este SocketChannel en el selector asociado con eventLoop.

Resumamos el proceso de registro de Channel:

1. Primero, en el método initAndRegister () de AbstractBootstrap, llame a group (). Register (channel)

El método register () de MultithreadEventLoopGroup.

2. En el registro () de MultithreadEventLoopGroup, llame al método next () para obtener un SingleThreadEventLoop disponible, y luego llame a su método register ().

3. En el método register () de SingleThreadEventLoop, llame al método channel.unsafe (). Register (this, promise) para obtener

Se llama al objeto de operación subyacente inseguro () del canal, y luego se llama al registro inseguro ().

4. En el método register () de AbstractUnsafe, llame al método register0 () para registrar el objeto Channel.

5. En el método register0 () de AbstractUnsafe, llame al método doRegister () de AbstractNioChannel.

6. El método doRegister () de AbstractNioChannel registra Java NIO SocketChannel correspondiente al canal en un selector eventLoop a través de javaChannel (). Register (eventLoop (). Selector, 0, this), y asocia el canal actual con el SocketChannel como un adjunto .

En general, el trabajo que realiza el proceso de registro del Canal es asociar el Canal con el EventLoop correspondiente, por lo que esto también se refleja en el Netty

Cada canal está asociado con un EventLoop específico, y todas las operaciones de IO en este canal se ejecutan en este EventLoop; cuando el canal y el EventLoop están asociados, el registro () del objeto SocketChannel del Java NIO subyacente continuará siendo llamado Método, registre el SocketChannel del Java NIO subyacente en el selector especificado. A través de estos dos pasos, se completa el proceso de registro de Netty to Channel.

El proceso de agregar Handler

Una de las características poderosas y flexibles de Netty es el mecanismo de manejo personalizado basado en Pipeline. En base a esto, podemos combinar libremente varios controladores para completar la lógica empresarial, como agregar complementos. Por ejemplo, si necesitamos procesar datos HTTP, podemos agregar un controlador para la codificación y decodificación HTTP antes de la canalización, y luego agregar nuestro propio controlador de lógica empresarial, de modo que el flujo de datos en la red sea el mismo que pasar a través de una canalización, desde diferentes Fluye a través del controlador, realiza la codificación y decodificación y finalmente llega a nuestro controlador personalizado.

Habiendo dicho eso, algunos amigos definitivamente sentirán curiosidad. Dado que este mecanismo de canalización es tan poderoso, ¿cómo se implementa? No lo voy a explicar en detalle aquí. En esta sección, comenzaremos con lo simple y con experiencia primero Cómo y cuándo se agrega el controlador personalizado a ChannelPipeline. Primero miramos el siguiente fragmento de código de usuario:

ChannelInitializer<SocketChannel> initializer = new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                //先添加websocket相关的编解码器和协议处理器
                pipeline.addLast("msgDecoder", new ProtocolDecoder());// 解码器
                pipeline.addLast("msgEncoder", new ProtocolEncoder());// 编码器
                pipeline.addLast("idleStateHandler", new IdleStateHandler(serverConfig.getAllIdleSecond(), 0, 0));// 定时器,秒
                pipeline.addLast("handler", new WebsocketRouterHandler());// 消息总处理器WebsocketRouterHandler

            }

        };

Este fragmento de código implementa la función de adición del controlador. Vemos que el método handler () de Bootstrap recibe un ChannelHandler, y el parámetro que pasamos es una clase anónima derivada de la clase abstracta ChannelInitializer, que por supuesto también implementa la interfaz ChannelHandler. Echemos un vistazo al misterio en la clase ChannelInitializer:

public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelInitializer.class);
    // We use a Set as a ChannelInitializer is usually shared between all Channels in a Bootstrap /
    // ServerBootstrap. This way we can reduce the memory usage compared to use Attributes.
    private final Set<ChannelHandlerContext> initMap = Collections.newSetFromMap(
            new ConcurrentHashMap<ChannelHandlerContext, Boolean>());

    /**
     * This method will be called once the {@link Channel} was registered. After the method returns this instance
     * will be removed from the {@link ChannelPipeline} of the {@link Channel}.
     *
     * @param ch            the {@link Channel} which was registered.
     * @throws Exception    is thrown if an error occurs. In that case it will be handled by
     *                      {@link #exceptionCaught(ChannelHandlerContext, Throwable)} which will by default close
     *                      the {@link Channel}.
     */
    protected abstract void initChannel(C ch) throws Exception;

    @Override
    @SuppressWarnings("unchecked")
    public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        // Normally this method will never be called as handlerAdded(...) should call initChannel(...) and remove
        // the handler.
        if (initChannel(ctx)) {
            // we called initChannel(...) so we need to call now pipeline.fireChannelRegistered() to ensure we not
            // miss an event.
            ctx.pipeline().fireChannelRegistered();

            // We are done with init the Channel, removing all the state for the Channel now.
            removeState(ctx);
        } else {
            // Called initChannel(...) before which is the expected behavior, so just forward the event.
            ctx.fireChannelRegistered();
        }
    }

    /**
     * Handle the {@link Throwable} by logging and closing the {@link Channel}. Sub-classes may override this.
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        if (logger.isWarnEnabled()) {
            logger.warn("Failed to initialize a channel. Closing: " + ctx.channel(), cause);
        }
        ctx.close();
    }

    /**
     * {@inheritDoc} If override this method ensure you call super!
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel().isRegistered()) {
            // This should always be true with our current DefaultChannelPipeline implementation.
            // The good thing about calling initChannel(...) in handlerAdded(...) is that there will be no ordering
            // surprises if a ChannelInitializer will add another ChannelInitializer. This is as all handlers
            // will be added in the expected order.
            if (initChannel(ctx)) {

                // We are done with init the Channel, removing the initializer now.
                removeState(ctx);
            }
        }
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        initMap.remove(ctx);
    }

    @SuppressWarnings("unchecked")
    private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
        if (initMap.add(ctx)) { // Guard against re-entrance.
            try {
                initChannel((C) ctx.channel());
            } catch (Throwable cause) {
                // Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...).
                // We do so to prevent multiple calls to initChannel(...).
                exceptionCaught(ctx, cause);
            } finally {
                ChannelPipeline pipeline = ctx.pipeline();
                if (pipeline.context(this) != null) {
                    pipeline.remove(this);
                }
            }
            return true;
        }
        return false;
    }

    private void removeState(final ChannelHandlerContext ctx) {
        // The removal may happen in an async fashion if the EventExecutor we use does something funky.
        if (ctx.isRemoved()) {
            initMap.remove(ctx);
        } else {
            ctx.executor().execute(new Runnable() {
                @Override
                public void run() {
                    initMap.remove(ctx);
                }
            });
        }
    }
}

ChannelInitializer es una clase abstracta. Tiene un método abstracto initChannel (). La clase anónima que vemos implementa este método.

Y agregue el controlador personalizado en este método. Entonces, ¿dónde se llama initChannel ()? De hecho, está en el método channelRegistered () de ChannelInitializer.

A continuación, céntrese en el método channelRegistered (). A partir del código fuente anterior, podemos ver que en el método channelRegistered (), se llamará al método initChannel (), se agregará el controlador personalizado a ChannelPipeline, y luego se llamará al método ctx.pipeline (). Remove (this) Eliminado de ChannelPipeline. El proceso de análisis anterior se muestra en la siguiente imagen:

Al principio, solo había tres controladores en ChannelPipeline: head, tail y ChannelInitializer que agregamos.

Luego, después de llamar al método initChannel (), se agrega un controlador personalizado:

Supongo que te gusta

Origin blog.csdn.net/madongyu1259892936/article/details/110442812
Recomendado
Clasificación