Java NIO: Implementación eficiente de E/S no bloqueante

Conceptos fundamentales de Java NIO

Java NIO proporciona un enfoque diferente para las operaciones de entrada/salida, basado en canales y búferes, diseñado para aplicaciones de alta concurrencia. A diferencia de las E/S tradicionales, NIO permite opearciones no bloqueantes mediante selectores.

Comparación entre modelos de E/S

El modelo BIO (Blocking I/O) es síncrono y bloqueante, asignando un hilo por conexión, lo que puede agotar recursos. NIO utiliza un modelo no bloqueante donde un solo hilo gestiona múltiples conexiones mediante multiplexación de E/S.

Componentes esenciales de NIO

NIO se estructura en tres pilares: Canales para la comunicación, Búferes para el almacenamiento temporal y Selectores para la multiplexación de eventos.

Canales de archivo

Para operar con archivos, se utiliza FileChannel. Ejemplo de lectura de archivo:


FileChannel canal = FileChannel.open(Paths.get("entrada.dat"), StandardOpenOption.READ);
ByteBuffer bufferLectura = ByteBuffer.allocate(4096);

int bytesLeidos;
while ((bytesLeidos = canal.read(bufferLectura)) > 0) {
    bufferLectura.flip();
    while (bufferLectura.hasRemaining()) {
        byte dato = bufferLectura.get();
        // Procesamiento del dato
    }
    bufferLectura.compact();
}
canal.force(true);
canal.close();

Canales de socket para clientes

SocketChannel permite la comunicación TCP no bloqueante desde el cliente:


SocketChannel cliente = SocketChannel.open();
cliente.configureBlocking(false);
cliente.connect(new InetSocketAddress("servidor.local", 8443));

while (!cliente.finishConnect()) {
    // Espera activa o ejecución de otras tareas
}

ByteBuffer bufferEnvio = ByteBuffer.wrap("Mensaje de prueba".getBytes());
cliente.write(bufferEnvio);
cliente.close();

Canales de socket para servidores

ServerSocketChannel gestiona conexiones entrantes en el servider:


ServerSocketChannel servidor = ServerSocketChannel.open();
servidor.bind(new InetSocketAddress(8080));
servidor.configureBlocking(false);

Selector selector = Selector.open();
servidor.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
    selector.select(1000);
    Set<selectionkey> clavesSeleccionadas = selector.selectedKeys();
    Iterator<selectionkey> iterador = clavesSeleccionadas.iterator();
    
    while (iterador.hasNext()) {
        SelectionKey clave = iterador.next();
        if (clave.isAcceptable()) {
            SocketChannel clienteConectado = servidor.accept();
            clienteConectado.configureBlocking(false);
            clienteConectado.register(selector, SelectionKey.OP_READ);
        } else if (clave.isReadable()) {
            SocketChannel canalCliente = (SocketChannel) clave.channel();
            ByteBuffer bufferCliente = ByteBuffer.allocate(1024);
            canalCliente.read(bufferCliente);
            // Procesamiento de datos recibidos
        }
        iterador.remove();
    }
}
</selectionkey></selectionkey>

Gestión de búferes

Los búferes son regiones de memoria para almacenamiento temporal de datos. Su uso requiere manejo cuidadoso de modos lectura/escritura mediante métodos como flip(), compact() y clear().


ByteBuffer buffer = ByteBuffer.allocateDirect(2048); // Búfer fuera del heap
buffer.putInt(12345);
buffer.putDouble(6.789);
buffer.flip();

int valorEntero = buffer.getInt();
double valorDouble = buffer.getDouble();
buffer.clear();

Optimización con mapeo de memoria

MappedByteBuffer permite mapear archivos directamente en memoria, reduciendo copias de datos entre espacio de usuario y kernel:


try (FileChannel archivo = FileChannel.open(Paths.get("grandes.dat"), StandardOpenOption.READ)) {
    MappedByteBuffer bufferMapeado = archivo.map(
        FileChannel.MapMode.READ_ONLY, 0, archivo.size()
    );
    while (bufferMapeado.hasRemaining()) {
        byte[] bloque = new byte[1024];
        bufferMapeado.get(bloque);
        // Procesamiento del bloque
    }
}

Selectores para multiplexación

Los selectores permiten monitorear múltiples canales en un solo hilo. El proceso implica registrar canales, sondear eventos y procesar claves de selección.


Selector monitor = Selector.open();
ServerSocketChannel servidorCanal = ServerSocketChannel.open();
servidorCanal.configureBlocking(false);
servidorCanal.register(monitor, SelectionKey.OP_ACCEPT);

while (monitor.select() > 0) {
    Set<selectionkey> eventos = monitor.selectedKeys();
    for (SelectionKey evento : eventos) {
        if (evento.isConnectable()) {
            SocketChannel canal = (SocketChannel) evento.channel();
            canal.finishConnect();
            canal.register(monitor, SelectionKey.OP_READ);
        } else if (evento.isReadable()) {
            SocketChannel canalLectura = (SocketChannel) evento.channel();
            ByteBuffer bufferTemporal = ByteBuffer.allocate(512);
            canalLectura.read(bufferTemporal);
            // Decodificación y procesamiento
        }
    }
    eventos.clear();
}
</selectionkey>

Etiquetas: Java NIO Channel Buffer Selector SocketChannel

Publicado el 6-5 18:48