Java NIO (Nueva entrada/salida) es una estructura y una API de manejo de archivos y redes de alto rendimiento que funciona como una API IO alternativa para Java. Se presenta a partir de JDK 4 , que funciona como el segundo sistema de E/S después de Java IO estándar con algunas características avanzadas adicionales. Proporciona soporte mejorado para las funciones del sistema de archivos y el manejo de archivos. Debido a las capacidades admitidas por las clases de archivos NIO, se usa ampliamente en el manejo de archivos. El paquete java.nio define las clases de búfer que se utilizan en las API de NIO. Fue desarrollado para permitir a los programadores de Java implementar E/S de alta velocidad sin utilizar el código nativo personalizado.
Las características principales de Java NIO son:
- Java NIO es una E/S asíncrona o una E/S sin bloqueo . Por ejemplo, un hilo necesita algunos datos del búfer. Mientras el canal lee datos en el búfer, el subproceso puede hacer otra cosa. Una vez que los datos se leen en el búfer, el subproceso puede continuar procesándolos.
- Java NIO tiene un enfoque orientado al búfer . Los datos se leen en un búfer y se almacenan en caché allí. Cada vez que se requieren los datos, se procesan más desde el búfer.
Java NIO se creó en tres componentes principales: búferes, canales y selectores.
- Búferes: el búfer es un bloque de memoria que se utiliza para almacenar temporalmente datos mientras se mueven de un lugar a otro.
- Canales: Channel representa una conexión a objetos que son capaces de realizar operaciones de E/S, como archivos y sockets.
- Selectores: Se utilizan para seleccionar los canales que están listos para la operación de E/S.
Servidores bloqueantes vs no bloqueantes
Un servidor sin bloqueo significa que puede tener varias requests en curso al mismo tiempo por el mismo proceso o subproceso porque utiliza E/S sin bloqueo . Entendámoslo con un ejemplo simple de la siguiente manera:
Un proceso de bloqueo es como una cola en las taquillas. Cada cliente es uno detrás de otro, nadie avanza hasta que la persona que está al frente de la cola ha sido atendida. Mientras que un proceso sin bloqueo podría pensarse como un mesero en un restaurante, tratando de servir a las personas a la vez, pasando por ellos y manejando sus comidas.
A. Servidores de bloqueo
Un servidor que usa un socket de bloqueo funciona de manera síncrona, lo que significa que cada vez que recibe una solicitud, la completa y luego atiende al otro. Aquí, los subprocesos múltiples entran en escena. Supongamos que hay tres requests en la cola, luego el servidor responderá a la primera y luego a la otra, y así sucesivamente. Hace que los clientes esperen más tiempo para lograr el objetivo deseado. Como se muestra en la imagen, se crean múltiples subprocesos para atender todas y cada una de las requests, que es una tarea que requiere un uso intensivo de la CPU.
Las llamadas al sistema que suspenden o ponen el proceso de llamada en espera hasta que ocurra el evento (en el que se bloqueó la llamada), después de lo cual el proceso bloqueado se activa y está listo para la ejecución son llamadas al sistema de bloqueo. Por ejemplo, una vez que un subproceso invoca una llamada, el modo de ejecución se transfiere al kernel y la llamada se bloquea hasta que se completa un determinado evento, el subproceso de llamada en particular que se ejecuta en el modo kernel se bloquea y se pone en algunos cola de espera Una vez que se completa el evento, el subproceso se activa y se coloca nuevamente en la cola de espera de la CPU.
B. Servidores sin bloqueo
En el enfoque sin bloqueo , un subproceso puede manejar varias consultas a la vez. Un servidor que utiliza un socket sin bloqueo funciona de manera asíncrona, lo que significa que una vez que se recibe la solicitud, permite que el sistema realice la tarea en él y continúa tomando otras requests y, una vez que la tarea finaliza, responde de acuerdo con cada solicitud. Como se muestra en la imagen, solo tiene un hilo y todo el proceso se lleva a cabo utilizando un concepto llamado «Event Loop» . Aquí, el sistema no está inactivo y nunca espera a que se completen todas y cada una de las tareas antes de aceptar otras requests.
Por ejemplo, tome la llamada al sistema read() que lee datos de un archivo en el búfer asignado por el programa de usuario. La llamada regresa al espacio de usuario solo después de que el búfer del usuario se haya llenado con los datos deseados. Básicamente, el subproceso se pone a dormir solo para ser despertado más tarde cuando el control del disco ha completado la E/S desde el dispositivo de almacenamiento y ha puesto los datos en el búfer de espacio del núcleo.
Sugerencia: El IO sin bloqueo se implementa en java.nio.package mediante el cual podemos crear servidores sin bloqueo.
Operaciones de lectura/escritura del servidor hipotético
Como se discutió anteriormente en el modelo sin bloqueo , un solo proceso puede manejar múltiples requests simultáneas al intercalar llamadas de E/S sin bloqueo para todas las requests. Una lectura sin bloqueo no hace que el proceso espere a que el otro extremo envíe datos, regresa inmediatamente e indica que aún no hay nada que leer. Por lo tanto, puede aceptar una nueva solicitud, leer de otro cliente o verificar si la base de datos ya ha arrojado resultados.
modelo de bloqueo
Primero tomemos el ejemplo del modelo de bloqueo en el que un solo hilo/proceso dentro del servidor maneja dos requests:
- solicitud A se conecta
- el hilo acepta la conexión A
- solicitud de lectura A
- solicitud B se conecta
- Todavía leyendo de A.
- analizar solicitud A
- obteniendo datos de la base de datos para A
- escribir resultados en A
- cerrar la conexión A
- el hilo acepta la conexión B
- leyendo de B.
- analizar solicitud B
- obteniendo datos de la base de datos para B
- escribir resultados en B
- cerrar la conexión B
Aquí la solicitud B realmente se conecta en el paso 4 , pero su conexión no se puede aceptar ni analizar hasta el paso 10 . La conexión se acepta solo después de que se cumple la solicitud A y se finaliza su conexión. La atención a la solicitud A no se interrumpe para atender la solicitud B hasta que se cumple la solicitud A. Aquí el proceso solo puede manejar una sola solicitud a la vez.
Modelo sin bloqueo
Ahora tomemos el ejemplo del modelo Non-Blocking en el que un solo hilo/proceso dentro del servidor maneja dos requests:
- solicitud A se conecta
- el hilo acepta la conexión A
- solicitud de lectura A
- solicitud B se conecta
- el hilo acepta la conexión B
- todavía leyendo de A
- solicitud de lectura B
- analizar solicitud A
- todavía leyendo de B
- obteniendo datos de la base de datos para A
- escribir resultados en A
- analizar solicitud B
- obteniendo datos de la base de datos para B
- cerrar la conexión A
- escribir resultados en B
- cerrar la conexión B
Aquí la solicitud B se conecta en el paso 4 y el subproceso la acepta inmediatamente en el paso 5 . Ahora el procesamiento de la solicitud B se intercala con el procesamiento de la solicitud A. Aquí el proceso puede manejar dos requests a la vez que asigna espacios para dos conexiones y dos requests.
Java NIO permite administrar múltiples canales utilizando un solo hilo. El canal Java NIO conecta el búfer y una entidad en otro extremo. En otras palabras, los canales se utilizan para leer datos en el búfer y también para escribir datos desde el búfer. El canal Java NIO admite el flujo asíncrono de datos tanto en modo de bloqueo como de no bloqueo.
SocketChannel y ServerSocketChannel son las dos clases que implementaron el canal Java NIO. Puede leer y
escribir los datos a través de conexiones TCP.
Composición de servidores sin bloqueo
Los servidores sin bloqueo tienen internamente una canalización de E/S sin bloqueo, que es una string de componentes que procesan tanto la lectura como la escritura de E/S sin bloqueo.
Como se muestra en la imagen de arriba, un componente usa un selector para verificar cuándo un canal tiene datos para leer. El componente lee los datos de entrada y genera una salida basada en la entrada dada. La salida se escribe de nuevo en un canal. Una canalización de E/S sin bloqueo realiza operaciones de lectura/escritura o solo puede leer o escribir datos. El componente inicia la lectura de datos del Canal a través del Selector . Como Java NIO realiza las operaciones de E/S sin bloqueo, los selectores y las teclas de selección con canales seleccionables definen las operaciones de E/S multiplexadas.
Las canalizaciones de E/S sin bloqueo leen datos de un socket o archivo y los dividen en mensajes ordenados lógicamente o integrados. Esto es similar a dividir un flujo de datos en tokens para analizarlos usando la clase StreamTokenizer en Java. Mientras que una canalización de Blocking IO utiliza una interfaz similar a InputStream donde solo se puede leer un byte a la vez desde Channel y bloquea hasta que haya datos listos para leer.
El modelo Non-Blocking utiliza Java NIO Selector para verificar y proporcionar solo aquellas instancias de SelectableChannel que realmente tienen algunos datos para leer para evitar verificar flujos que tienen 0 bytes para leer.
Tuberías sin bloqueo
En general, podemos suponer que un servidor sin bloqueo termina con tres «tuberías» que se ejecutan repetidamente en un bucle:
- La canalización de lectura comprueba si hay nuevos datos entrantes o cualquier mensaje completo nuevo de las conexiones abiertas.
- La canalización de procesamiento procesa los mensajes completos entrantes.
- La canalización de escritura comprueba si puede escribir mensajes salientes en cualquiera de las conexiones abiertas.
Si no hay mensajes alineados, se puede omitir la canalización de escritura, o si no hay nuevos datos entrantes o mensajes completos nuevos, entonces se puede omitir la canalización de procesamiento.