¿Cómo se vincula la nueva conexión recibida por el servidor Netty con el grupo de subprocesos de trabajo?

 

Más intercambio técnico puede seguirme

Prólogo

Original: ¿cómo se vincula la nueva conexión recibida por el servidor Netty con el grupo de subprocesos de trabajo?

En el análisis anterior del proceso de detección de nuevas conexiones en el servidor Netty, se mencionó que después de leer la nueva conexión, el NioServerSocketChannel llamará cíclicamente al método pipeline.fireChannelRead () vinculado al Canal del servidor, empacará cada nueva conexión como parámetro y luego pasará este método a Pasa a lo largo de la canalización del canal del servidor, es decir, fluye en la cadena del controlador del canal. Esta parte de los detalles se desglosará en detalle más adelante. 

Echemos un vistazo a cómo el grupo de subprocesos jefe y el grupo de subprocesos de trabajo configurados por Netty cooperan en el flujo de la tubería conectada al nuevo canal en el lado del servidor.

Análisis del código fuente del nuevo acceso de conexión del servidor

Revise brevemente el artículo anterior: ¿Cómo maneja Netty los nuevos eventos de acceso a la conexión? En el análisis del proceso de detección de nuevas conexiones en el servidor Netty, recupere el código fuente del método read () de la clase NioMessageUnsafe:

Mirando el último cuadro rojo, la nueva conexión se pasa a lo largo de la canalización del canal en el bucle. NioMessageUnsafe es la interfaz interna de la clase de implementación del servidor Netty Channel-Unsafe mencionada anteriormente.

Entonces, ¿qué sucede cuando estas nuevas conexiones se entregan posteriormente? Este también es un tema clave, es decir, cómo el canal recién conectado del cliente Netty está asociado con el hilo de E / S de Netty después de ser encapsulado. Veamos el nuevo acceso de conexión mencionado anteriormente. La función asociada se realiza principalmente por este acceso.

Para volver a la verdad, mire el código fuente de ServerBootstrapAcceptor, que es una clase interna que hereda ChannelInboundHandlerAdapter (más tarde explique el mecanismo de canalización de Netty).

Ahora revise primero el proceso de inicio del servidor. La operación principal del inicio del servidor es vincular el puerto, es decir, comenzar en el método serverBootstrap.bind (xx); en el código de usuario, que llamará al método doBind de ServerBootstrap, y llamará al método initAndRegister () de ServerBootstrap en el método doBind, que es Un método para inicializar el canal del servidor y registrar el multiplexor de E / S, como se muestra a continuación:

Este método crea un NioServerSocketChannel en el lado del servidor a través de la reflexión, y crea un ServerSocketChannel que almacena el JDK y algunos componentes, como tuberías, etc., y luego realiza la operación de inicialización del canal, es decir, el método init (canal) de ServerBootstrap (el análisis es el código del servidor, por lo que Solo mire la implementación de init por la clase ServerBootstrap), hay una lógica para crear un nuevo acceso de conexión en el método init. Como se muestra en el cuadro rojo a continuación, al configurar la canalización del servidor en init, se agrega un controlador ServerBootstrapAcceptor de manera predeterminada:

Primero complete todo el proceso:

1. Primero, el método init de ServerBootstrap agrega un ChannelInitializer a la canalización del canal del servidor. En el método void initChannel (Channel ch) implementado por esta clase, el controlador del servidor configurado en el código de usuario se agrega primero. Como dije antes, esto La configuración del controlador en el lado del servidor rara vez se utiliza (es decir, API Handler ()), y la más utilizada es configurar el controlador para el cliente, es decir, ChildHandler ()

2. Luego agregue un nuevo acceso de conexión asíncrono: ServerBootstrapAcceptor. Específicamente, la operación de agregar ServerBootstrapAcceptor a la tubería se encapsula en una tarea, y el subproceso NIO delegado al servidor se ejecuta de forma asíncrona. Cuando llega una nueva conexión, la tarea Ha sido ejecutado Es decir, la estructura de canalización mínima del canal del servidor Netty es la siguiente:

Aquí entramos en contacto con el concepto de los eventos entrantes y salientes de Netty por adelantado. Los llamados eventos entrantes, es decir, eventos entrantes, es decir, el hilo NIO de Netty se inicia activamente, es una operación para manejadores de negocios de usuarios, es decir, eventos que se inician pasivamente Difundir a través del método fireXXX.

Por ejemplo, la conexión del canal es exitosa, el canal está cerrado, el canal tiene datos para leer, el multiplexor de E / S registrado en el canal es exitoso, el canal cancela el registro del multiplexor de E / S y se lanza la excepción. Estas son ejecuciones pasivas. Los eventos de devolución de llamada, su procesamiento tiene una implementación de controlador especial, controlador de entrada de llamada unificada. Por el contrario, hay eventos salientes y manejadores salientes. Los eventos salientes-eventos salientes, son eventos iniciados por hilos de usuario o código de usuario. Los siguientes son eventos salientes:

Por ejemplo, el servidor vincula activamente el puerto, cierra activamente la conexión, el cliente se conecta activamente al servidor y el servidor (cliente) escribe mensajes activamente. Las características de estos eventos son iniciadas por el usuario. Para estos dos tipos de eventos, además de los controladores proporcionados por Netty por defecto, los usuarios también pueden personalizar los controladores entrantes / salientes para implementar su propia lógica de intercepción, que también es la idea del modelo de cadena de responsabilidad (también llamada cadena de responsabilidad).

Después de todo, continuamos analizando el proceso del servidor que lee la nueva conexión. Ahora estamos analizando el nuevo acceso a la conexión, por lo que solo miramos el controlador entrante. Primero sepa que la secuencia del flujo de eventos entrantes comienza desde el nodo principal de la tubería, pasa a través de cada nodo controlador entrante y fluye hasta el final del nodo de cola. Aquí está Head-> ServerBootstrapAcceptor-> Tail. Como sigue:

También debe saber que el nodo de cola es esencialmente un controlador entrante y que el nodo principal es esencialmente un controlador de salida, que se desmantelará en detalle más adelante. No importa por qué no sabe por qué.

Como se mencionó anteriormente, el método read () de la clase NioMessageUnsafe finalmente pasará la nueva conexión del cliente read, de la siguiente manera:

Específicamente, activa el evento ChannelRead posterior de cada controlador entrante (se dice que channelRead es un evento entrante). Los eventos entrantes se propagan desde el nodo principal de la canalización, HeadContext, y este evento se activa para propagarse. Es el método pipeline.fireChannelRead (xxx).

Recuerde que cuando se inició el servidor, hay un código de la siguiente manera: yserverBootstrap.handler(new ServerHandler())serverBootstrap.childHandler(new ServerHandler());

En ese momento, llegué a esta conclusión: el controlador agregado por el método .handler se agrega a la canalización del canal del servidor, que se agrega cuando se inicializa el servidor, y el controlador agregado por el método .childHandler se agrega a la tubería del canal del cliente , Se agrega al manejar el nuevo acceso a la conexión. Ahora que se conoce el motivo, cuando ServerBootstrap llama a init, primero pipeline.addLast (controlador) y luego agrega un ServerBootstrapAccepter, de modo que la tubería del servidor también puede ser head-hander> serverBootStrapAccepter> seguir esta estructura, de la siguiente manera (estructura muy familiar):

Debe entenderse aquí que las dos operaciones agregan el controlador a la canalización del servidor y el cliente respectivamente.

serverBootStrapAccepter en sí mismo también es un controlador entrante. De acuerdo con el análisis anterior, la secuencia de propagación de eventos entrantes es head-> handbound inbound handler-> ServerBootstrapAcceptor-> tail. No definí un controlador para el servidor en mi demo, así que llamé directamente al método channelRead de ServerBootstrapAcceptor, que es El punto clave del dispositivo de acceso debe estudiarse: el código fuente del método channelRead de ServerBootstrapAcceptor es el siguiente;

ServerBootstrapAcceptor es una clase interna de ServerBootstrap. Echemos un vistazo al proceso de depuración: una vez que aparece, forzará msg en el canal, es decir, la variable msg recibida aquí es esencialmente la nueva conexión del cliente que Netty acaba de encapsular en lectura como su canal personalizado. El posterior ServerBootstrapAcceptor hizo principalmente tres cosas:

1. El amarillo es el análisis anterior. Agregue el controlador de canal del cliente configurado por el usuario en el descriptor de acceso: es decir, agregue el usuario a la canalización del cliente a través del ChannelHandler personalizado .childHandler () en el código del servidor, que se explicará en detalle más adelante.

2. En amarillo 2, configure las opciones y atributos configurados por el usuario, principalmente para configurar las opciones childOptions y childAttrs del canal del cliente, childOptions son los atributos configurados para el protocolo TCP en la parte inferior del canal, childAttrs son algunos atributos del canal en sí, su esencia es un mapa, Por ejemplo, puede almacenar el tiempo de supervivencia del canal actual, la clave, etc.

3. En amarillo 3, seleccione un subproceso NIO en el grupo de subprocesos de trabajo y vincúlelo al canal del cliente, la variable secundaria en el código. Este paso es una operación asincrónica y se implementa a través del método de registro. Este método reutiliza la lógica de código para registrar el multiplexor de E / S para el canal del servidor cuando se inicia el servidor. Este último paso se divide en dos pequeños pasos:

  • El grupo de subprocesos de trabajo selecciona un subproceso NioEventLoop para unirse a la nueva conexión a través del método next () selector de subprocesos de Choque de EventLoop, que tiene la misma lógica que el grupo de subprocesos del lado del servidor

  • Registre el nuevo canal del cliente en el multiplexor de E / S de este NioEventLoop y registre el evento OP_READ para él

Los dos pequeños pasos se analizan en detalle a continuación. Seguí el registro a través de la depuración y llegué al método de registro de MultithreadEventLoopGroup. El código fuente es el siguiente:

Finalmente, ingrese la clase primaria io / netty / util / concurrent / MultithreadEventExecutorGroup, vea aquí es muy familiar, ingresará el selector de subprocesos del NioEventLoopGroup analizado previamente.

El método de optimización utilizado aquí: seleccione un subproceso NioEventLoop por operación de bit. Se encuentra que idx es 0, es decir, el subproceso en el grupo de subprocesos de grupo de trabajo acaba de seleccionar el primero en este momento, porque esta es la primera conexión de cliente recibida por el servidor que estoy ejecutando actualmente, por lo que cuando se trata de una nueva conexión, estará en orden. Inicie el subproceso posterior para enlazarlo, si está vinculado al último, entonces idx comenzará desde 0 nuevamente y realizará un ciclo de ida y vuelta. . . Tenga en cuenta que el hilo NIO aún no se ha iniciado. Netty se ha optimizado, como se mencionó anteriormente, el grupo de subprocesos de Netty se retrasa el inicio.

Después de seleccionar el hilo NioEventLoop en el método de registro de la clase MultithreadEventLoopGroup, el método next () devolverá una instancia de NioEventLoop, y luego continuará llamando al método de registro de la instancia, es decir, el siguiente paso saltará al método de registro de la clase primaria directa SingleThreadEventLoop del NioEventLoop, el siguiente código fuente :

Llamado al segundo método de registro, el método channel () en el interior devuelve el NioSocketChannel del cliente, y el método unsafe () es una instancia de NioByteUnsafe, que finalmente llama al método de registro inseguro del canal del cliente. Esa es la clase interna del método de registro AbstractChannel-AbstractUnsafe, el código fuente es el siguiente:

El código de este método debería ser muy familiar. Lo analicé antes de que se iniciara el servidor Netty, es decir, la lógica de registrar el multiplexor de E / S para la nueva conexión del cliente reutilizó este conjunto de código, que También se benefició del buen diseño arquitectónico de Netty.

Analicemos la lógica de ejecutar el método de registro de AbstractUnsafe:

1. Primero verifique el hilo de E / S y el canal del cliente actual, y luego en el amarillo 1, determine si el hilo actual es un hilo NIO, obviamente aquí es falso, porque aunque se ha seleccionado un hilo NIO del cliente en este momento, pero El subproceso NIO no se ha iniciado, toda la lógica de registro todavía se está ejecutando bajo el subproceso de usuario, mi demostración es el subproceso principal, como se evidencia a continuación, por lo que 1 falla aquí, y luego ejecuta el código para delegar la lógica de registro real al recién iniciado El subproceso NIO del cliente se ejecuta de forma asincrónica, lo que también garantiza la seguridad del subproceso.

2. Observe los dos lugares amarillos, es decir, en el código else, el subproceso NIO previamente seleccionado se iniciará mediante el método de ejecución de NioEventLoop (por supuesto, si ya se ha iniciado, se omitirá el paso de inicio), mientras conduce la tarea registrada, esto realmente está aquí Iniciar el subproceso NIO también puede demostrar que el grupo de subprocesos de Netty ha logrado un inicio retrasado,

3. Finalmente, mirando los tres lugares amarillos, ingresé el método register0 y miré su código fuente de implementación, de la siguiente manera:

El método más crítico es el método doRegister (). Mira el cuadro rojo. Ingresé este método y descubrí que ahora está en la subclase AbstractNioChannel. Esto es muy familiar, o es el mismo que el proceso de registrar ServerSocketChannel en el lado del servidor, de la siguiente manera:

Es la lógica del Selector de canales registrado JDK que encapsula Netty. En este método, registre el canal del cliente en el multiplexor de E / S del hilo NioEventLoop del cliente y adjunte el objeto NioSocketChannel al canal JDK, pero el evento de E / S registrado de interés en este momento sigue siendo 0, lo que Sin atención, es decir, el canal del cliente todavía está en el estado de inicialización, y el evento de E / S de registro real todavía está en el proceso de retroceso.

Tenga en cuenta que este método escribe la lógica de registro en un bucle infinito. El propósito de aprender este uso es garantizar que una cosa se debe completar, incluso si se producen ciertas excepciones.

Regrese al método register0 y vuelva a mirar. Después de completar el registro, el evento handlerAdded en estado suspendido se activará primero, es decir, el código en amarillo 1 se ejecutará primero. Esto corresponde a agregar un controlador de cliente definido por el usuario para la nueva conexión del cliente. Lógica Luego, el amarillo 2 se ejecuta para activar y propagar el evento de que el canal actual se ha registrado con éxito. Si el canal actual todavía está vivo, continuará ejecutando el código en tres lugares, es decir, el evento de una conexión de canal exitosa (estado activo) se propaga para el primer canal nuevo registrado.

Finalmente, si el canal actual no se registra por primera vez, determinará si se configurará el mensaje de lectura automático (Netty se establece de manera predeterminada como prioridad de lectura), si es así, se ejecutará el código en amarillo 4 y la explicación detallada posterior.

Pequeño nudo

El núcleo de la asignación de subprocesos NIO para nuevas conexiones y el registro de multiplexores de E / S para nuevas conexiones es comprender ServerBootstrapAcceptor, y así conocer la composición mínima de canalización del canal del servidor: Head-> ServerBootstrapAcceptor-> Tail

理解 ServerBootstrapAcceptor:

1. Retraso para agregar childHandler: agregue un ChannelHandler personalizado a la tubería recién conectada. Debe agregarse después de que el canal actual haya registrado el multiplexor de E / S

2. Establecer opciones y attrs-set childOptions y childAttrs

3. Seleccione NioEventLoop y regístrese en el Selector. El núcleo es llamar al método next () del Selector del grupo de subprocesos de trabajo para seleccionar un NioEventLoop. A través de su método doRegister (), registre la nueva conexión al Selector vinculado al subproceso de trabajo. La nueva conexión aquí y el Selector tienen una relación de muchos a uno.

Bienvenida atención

El blog de dashuai es un profesional de aprendizaje permanente, un gran programador, y se centra en la experiencia laboral, el intercambio de notas de estudio y el vómito diario, que incluye, entre otros, la industria de Internet, con algunos libros electrónicos en PDF, materiales y ayuda para promocionar, bienvenido Paizhuan!

Supongo que te gusta

Origin www.cnblogs.com/kubixuesheng/p/12723456.html
Recomendado
Clasificación