Upload
fausto-guiterrez
View
5
Download
0
Embed Size (px)
Citation preview
Introducción Características de la nueva API E/S antes de Java 1.4 ¿Por qué NIO? E/S orientada a bloques El patrón Reactor Canales y Buffers Lectura y escritura en NIO Entendiendo el comportamiento de un buffer Mas sobre buffers File lockings Networking y E/S asincrónica Character sets
La API NIO (java.nio) es introducida en Java 1.4
Suplementa las facilidades de la API IO (java.io)
Provee nuevas características y mejora la performance en las áreas de:
₋ Manejo de buffers₋ Networking escalable₋ Entrada/Salida de archivos₋ Soporte de “character sets”₋ Expresiones regulares
Buffers para datos de tipos primitivos
Codificadores y decodificadores de “Character-sets”
Facilidad de “pattern-matching” basada en expresiones regulares estilo Perl
Interface de archivo que soporta bloqueos y mapeo de memoria
Facilidad de entrada salida multiplexada, no bloqueante, para implementar servidores escalables
Única forma de manipular E/S era mediante la abstracción de flujos (Streams), y sus versiones especializadas para caracteres Unicode (Readers y Writers)
Movimientos de bytes simples, uno a la vez, a través de objetos Stream
Un flujo de entrada “InputStream” produce un byte de datos
Un flujo de salida “OutputStream” consume un byte de datos
Ejemplo 1₋ Cliente/Servidor₋ Operaciones
bloqueantes
Ejemplo 2₋ Usar Threads para
permitir escalar₋ Problema: Thread
overhead
Implementación de E/S de alta velocidad sin necesidad de escribir código nativo
NIO traslada la mayoría de las operaciones “pesadas” (manejo de buffers) de E/S al sistema operativo
IO ha sido reimplementada utilizando NIO
Criterio principal en el diseño de la API NIO
Las aplicaciones servidor en un sistema distribuido deben manejar múltiples clientes enviando pedidos de servicio concurrentes
Previo a la invocación de un servicio, el servidor debe despachar cada pedido entrante al servicio correspondiente (en forma concurrente)
El patrón Reactor resuelve esta funcionalidad
Principales funcionalidades del patrón Reactor₋ Despachar eventos a manejadores de eventos
Objetos centrales de la API
Usados prácticamente en todas las operaciones de E/S
Canales (Channel)₋ Análogos a Streams en el paquete IO₋ Todos los datos deben pasar por un canal
Buffers
Un Buffer es un objeto contenedor de datos sobre el que se escriben, o del que se leen, datos
En NIO todos los datos son manejados con Buffers
Esencialmente es una secuencia (generalmente de bytes, pero puede ser de otro tipo), que provee acceso estructurado a los datos
Un Canal es un objeto del que se pueden leer (o al que se pueden escribir) datos.
Un canal es similar a un stream.
Todos los datos son manejados mediante Buffers.
Nunca se escribe/lee un byte directamente en/de el canal
Los canales son bidireccionales. Los streams son unidireccionales
Tipos de canales₋ Un stream debe ser subclase o bien de InputStream o
bien de OutputStream₋ Un Canal puede ser abierto para lectura, escritura o
ambas
Lectura de un canal:
₋ Creo un buffer₋ Solicito al canal
que coloque datos en él
Escritura sobre un canal:
₋ Creo un buffer₋ Lleno el buffer
con datos₋ Solicito al canal
que escriba los datos contenidos en el buffer
Estado₋ Cambian con cada lectura/escritura₋ Permiten que el buffer maneje sus propios
recursos Accessors
₋ get() - Obtiene datos del buffer₋ put() – Escribe datos en el buffer
Creo un buffer de 8 slots Posiciones válidas 0 a 7 Position = 0 Limit = Capacity = 8
Leo de un canal (por ejemplo 3 bytes) Position = 3 Limit = Capacity = 8
Segunda lectura (por ejemplo 2 bytes) Position = 5 Limit = Capacity = 8
Ejecuto flip() para poder leer del buffer1. Limit = Position = 52. Position = 03. Capacity = 8
Leo datos del buffer (escribo en un canal) Por ejemplo 4 bytes Limit = 5 Position = 4
Leo datos del buffer (escribo en un canal) Por ejemplo 1 byte (lo que resta) Limit = 5 Position = 5
Invoco clear Position = 0 Limit = Capacity = 8
Accessors en ByteBuffer₋ byte get()₋ ByteBuffer get(byte dst [])₋ ByteBuffer get(byte dst [], int offset, int lenght)₋ byte get(int index)
Los tres primeros son relativos (a position) y modifican el estado (position)
El cuarto es absoluto y no afecta el estado
put() en ByteBuffer₋ ByteBuffer put(byte b)₋ ByteBuffer put(byte src [])₋ ByteBuffer put(byte src [], int offset, int lenght)₋ ByteBuffer put(ByteBuffer src)₋ ByteBuffer put(int index, byte b)
Los cuatro primeros son relativos (a position) y modifican el estado (position)
El quinto es absoluto y no afecta el estado
get() y put() tipados₋ getByte()₋ getChar()₋ getShort()₋ getInt()₋ getLong()₋ getFloat()₋ getDouble()₋ putByte()₋ putChar()₋ putShort()₋ putInt()₋ putLong()₋ putFloat()₋ putDouble()
Asignación de espacio₋ Previo a utilizar un buffer es necesario reservar el
espacio para su contenido₋ Método estático allocate() crea el array interno
ByteBuffer buffer = ByteBuffer.allocate(1024);
Wrapping₋ Es posible convertir un array existente en un
Buffer
byte[] arr = new byte[1024];ByteBuffer buffer = ByteBuffer.wrap(arr);
₋ Cuidado: los datos pueden ser accedidos desde arr !!!
Slicing₋ El método slice() crea una especie de sub buffer
de un buffer₋ Datos compartidos con una porción del buffer
origen
ByteBuffer buffer = ByteBuffer.allocate( 10 );
for (int i=0; i<buffer.capacity(); ++i) { buffer.put( (byte)i );}
buffer.position(3);buffer.limit(7);ByteBuffer slice = buffer.slice();
Buffers de solo lectura₋ Usar el método asReadOnlyBuffer() para obtener
una copia de solo lectura del buffer₋ Comparte datos con el buffer original₋ Un buffer de solo lectura no puede convertirse en
un buffer de lectura/escritura
Scattering/Gattering₋ Lectura/Escritura de un canal usando múltiples
buffers₋ Útil para separar porciones de datos diferentes
(ejemplo: secciones de tamaño fijo en un paquete de un protocolo de red)
Interfaces: ScatteringByteChannel y GatheringByteChannel
No tiene que ver con prevenir cualquier uso de un archivo
Permite que distintas partes de la aplicación coordinen los archivos compartidos y la adquisición de locks
Se puede bloquear un archivo entero o una parte del mismo
A continuación, veremos un ejemplo en el cual se intenta abrir un archivo .txt utilizando los bloqueos de archivos.
E/S asincrónica₋ Una invocación a read() sobre un InputStream bloquea el hilo
actual hasta que hayan datos disponibles
₋ E/S asincrónica no es bloqueante
₋ La aplicación se registra por un evento de E/S particular (disponibilidad de nuevos datos, conexiones de socket, etc.) y el sistema notifica la llegada del evento
₋ Es posible escuchar por eventos en un número arbitrario de canales sin necesidad de polling o hilos extra
Selectores₋ Es el objeto sobre el
cual registrarse por eventos de E/S
₋ Notifica la ocurrencia de estos eventos
₋ Luego es necesario registrar los canales sobre el selector para notificarnos de la ocurrencia de eventos
http://tutorials.jenkov.com/java-nio/selectors.html
Abriendo un ServerSocketChannel₋ Objeto que recibe las solicitudes de conexiones
(uno por cada puerto)
El argumento OP_ACCEPT especifica que se va a escuchar por eventos accept (nueva conexión)
Cuando un Selector notifica un evento lo hace proveyendo la SelectionKey que corresponde al evento
La SelectionKey puede ser usada para desregistrar el canal
El loop interno Notificación de eventos
int num = selector.select();Set selectedKeys = selector.selectedKeys();Iterator it = selectedKeys.iterator();while (it.hasNext()) {
SelectionKey key = (SelectionKey)it.next();
// ... deal with I/O event ...}
Escuchando por nuevas conexiones
Consulto el tipo de eventoif ((key.readyOps() & SelectionKey.OP_ACCEPT)
== SelectionKey.OP_ACCEPT) {// Accept the new connection// ...
}
Luego de recibido el evento de solicitud de conexión, se acepta mediante la operación no bloqueante accept()
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();SocketChannel sc = ssc.accept();
Configuramos en nuevo SocketChannel como “no bloqueante”
Registramos el SocketChannel con el Selector para informarnos de los eventos de llegada de datos (OP_READ)
sc.configureBlocking(false);SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ);
A continuación, veremos un ejemplo en el cual se establece una conexión. Para ello, se utilizan sockets.
En este caso, según el tipo de la key enviada, la conexión será para escribir, leer, conectarse o aceptar conexión.
Un Charset es “un mapa nominado entre secuencias de caracteres Unicode de 16 bits y secuencias de bytes”
Permite leer y escribir secuencias de caracteres en la forma mas portable posible
Java se basa en Unicode
No todo se basa en Unicode
Charset permite codificar y decodificar entre distintas representaciones
₋ Ejemplo: Unicode <-> ISO-8859-1
Ejemplo₋ Se leen los datos de un archivo de texto (ISO-
8859-1) en un CharBuffer (usando un CharsetDecoder)
₋ Se recodifican en UTF16 y se escriben en un nuevo archivo usando un CharsetEncoder
Cualquier implementación de Java debe soportar₋ US-ASCII₋ ISO-8859-1₋ UTF-8₋ UTF-16BE₋ UTF-16LE₋ UTF-16
A continuación veremos un ejemplo en el cual se quiere convertir bytes ISO-8859-1 en un ByteBuffer, a un string en CharBuffer y viceversa.
Características de la nueva API E/S antes de Java 1.4 ¿Por qué NIO? E/S orientada a bloques El patrón Reactor Canales y Buffers Lectura y escritura en NIO Entendiendo el comportamiento de un buffer Mas sobre buffers File lockings Networking y E/S asincrónica Character sets
Getting started with New IO (NIO)Greg TravisIBM Developer workshttp://www-128.ibm.com/developerworks/edu/j-dw-java-nio-i.html
Sun New I/O APIs Guidehttp://java.sun.com/j2se/1.4.2/docs/guide/nio/
Java™ NIORon HitchensO’ReillyISBN: 0-596-00288-2, 312 pages