Java NIO proporciona una forma diferente de trabajar IO que la IO estándar:
Canales y búfer: IO estándar funciona en función de flujos de bytes y flujos de caracteres, mientras que NIO funciona en función de canales (canal) y búfer (búfer). Los datos siempre se leen del canal al área del búfer o se escriben desde el búfer al búfer. canal.
IO asincrónica: Java NIO le permite usar IO de forma asincrónica. Por ejemplo, cuando un subproceso lee datos de un canal en un búfer, el subproceso aún puede hacer otras cosas. Cuando los datos se escriben en el búfer, el hilo puede continuar procesándolos. Escribir en un canal desde un búfer es similar.
Selectores: Java NIO introduce el concepto de selectores, que se utilizan para escuchar eventos en múltiples canales (por ejemplo: apertura de conexión, llegada de datos). Por tanto, un único hilo puede escuchar múltiples canales de datos.
Presentemos en detalle los conocimientos relevantes de Java NIO.
Descripción general de Java NIO
Java NIO consta de las siguientes partes principales:
Canales
Amortiguadores
Selectores
Aunque hay muchas otras clases y componentes en Java NIO, en mi opinión, Channel, Buffer y Selector constituyen la API principal. Otros componentes, como Pipe y FileLock, son simplemente clases de utilidad que se utilizan con los tres componentes principales. Por lo tanto, en esta descripción general me centraré en estos tres componentes. Otros componentes se tratan en capítulos separados.
Canal y búfer
Básicamente, todas las IO en NIO comienzan desde un canal. Los canales son un poco como transmisiones. Los datos se pueden leer desde el canal al búfer o escribir desde el búfer al canal. Aquí hay una ilustración:
Hay varios tipos de Canales y Buffers. Las siguientes son las implementaciones de algunos canales principales en JAVA NIO:
Canal de archivos
Canal de datagramas
Canal de enchufe
ServidorSocketCanal
Como puede ver, estos canales cubren IO de red UDP y TCP, así como IO de archivos.
Junto con estas clases hay algunas interfaces interesantes, pero por simplicidad intenté no mencionarlas en la descripción general. Los explicaré en otros capítulos de este tutorial donde sean relevantes.
La siguiente es la implementación clave del búfer en Java NIO:
Búfer de bytes
CharBuffer
Doble búfer
búfer flotante
IntBuffer
búfer largo
búfer corto
Estos buffers cubren los tipos de datos básicos que puedes enviar a través de IO: byte, short, int, long, float, double y char.
Java NIO también tiene un Mappyteuffer, que se utiliza para representar archivos mapeados en memoria. No lo explicaré en la descripción general.
Selector
El selector permite que un solo hilo maneje múltiples canales. Si su aplicación abre múltiples conexiones (canales), pero el tráfico de cada conexión es muy bajo, usar Selector puede resultar conveniente. Por ejemplo, en un servidor de chat.
Esta es una ilustración del uso de un Selector para procesar 3 canales en un solo hilo:
Para usar un Selector, debe registrar un Canal con el Selector y luego llamar a su método select(). Este método se bloqueará hasta que un canal registrado tenga un evento listo. Una vez que este método regresa, el hilo puede manejar estos eventos. Ejemplos de eventos son la entrada de nuevas conexiones, la recepción de datos, etc.
Java NIO frente a IO
(Dirección original de esta parte, autor: Jakob Jenkov, traductor: Guo Lei, corrector: Fang Tengfei)
Después de aprender sobre Java NIO y IO API, inmediatamente me vino a la mente una pregunta:
Cita
¿Cuándo debo usar IO y cuándo debo usar NIO? En este artículo, intentaré explicar claramente las diferencias entre Java NIO e IO, sus escenarios de uso y cómo afectan el diseño de su código.
Principales diferencias entre Java NIO e IO
La siguiente tabla resume las principales diferencias entre Java NIO e IO. Describiré las diferencias en cada parte de la tabla con más detalle.
IO NIO
Orientado a flujo Orientado a búfer
IO bloqueante IO no bloqueante
Selectores
Orientado a flujos y orientado a búfer
La primera mayor diferencia entre Java NIO e IO es que IO está orientado al flujo y NIO está orientado al búfer. Java IO está orientado a la secuencia, lo que significa que se leen uno o más bytes de la secuencia a la vez y, hasta que se leen todos los bytes, no se almacenan en caché en ninguna parte. Además, no puede mover datos en la secuencia hacia adelante o hacia atrás. Si necesita mover los datos leídos de la secuencia de un lado a otro, primero debe almacenarlos en caché en un búfer. El enfoque orientado al búfer de Java NIO es ligeramente diferente. Los datos se leen en un búfer que procesa más tarde, moviéndose hacia adelante y hacia atrás en el búfer según sea necesario. Esto aumenta la flexibilidad en el procesamiento. Sin embargo, también debe verificar que el búfer contenga todos los datos que necesita procesar. Además, asegúrese de que a medida que se lean más datos en el búfer, los datos no procesados en el búfer no se sobrescriban.
IO bloqueante y no bloqueante
Se están bloqueando varias secuencias de Java IO. Esto significa que cuando un hilo llama a read() o write(), el hilo se bloquea hasta que se leen algunos datos o los datos se escriben por completo. El hilo no puede hacer nada más durante este período. El modo sin bloqueo de Java NIO permite que un hilo envíe una solicitud para leer datos de un determinado canal, pero solo puede obtener los datos disponibles actualmente. Si no hay datos disponibles actualmente, no se obtendrá nada. En lugar de mantener el hilo bloqueado, el hilo puede continuar haciendo otras cosas hasta que los datos sean legibles. Lo mismo ocurre con las escrituras sin bloqueo. Un hilo solicita escribir algunos datos en un canal, pero no necesita esperar a que se escriba por completo. Mientras tanto, el hilo puede hacer otras cosas. Los subprocesos suelen utilizar el tiempo de inactividad en IO sin bloqueo para realizar operaciones de IO en otros canales, por lo que un solo subproceso ahora puede administrar múltiples canales de entrada y salida.
Selectores
Los selectores de Java NIO permiten que un solo hilo monitoree múltiples canales de entrada. Puede registrar múltiples canales usando un selector y luego usar un hilo separado para "seleccionar" canales: estos canales ya tienen entradas que se pueden procesar. listo para escribir. Este mecanismo de selección facilita que un solo hilo administre múltiples canales.
Cómo NIO e IO impactan el diseño de aplicaciones
Ya sea que elija una caja de herramientas IO o NIO, existen varios aspectos que pueden afectar el diseño de su aplicación:
Llamadas API a clases NIO o IO.
Proceso de datos.
El número de subprocesos utilizados para procesar datos.
llamada API
Por supuesto, las llamadas a la API cuando se usa NIO se ven diferentes que cuando se usa IO, pero esto no es inesperado porque en lugar de simplemente leer byte a byte de un InputStream, los datos primero deben leerse en un búfer y luego procesarse.
Proceso de datos
Al utilizar el diseño NIO puro en comparación con el diseño IO, el procesamiento de datos también se ve afectado.
En el diseño de IO, leemos datos byte a byte de InputStream o Reader. Supongamos que está procesando un flujo de datos de texto basado en líneas, por ejemplo:
Copie el código de código de la siguiente manera:
Nombre: Ana
Edad: 25
Correo electrónico: [email protected]
Teléfono: 1234567890
El flujo de líneas de texto se puede manejar así:
Copie el código de código de la siguiente manera:
InputStream input =… // obtiene el InputStream del socket del cliente
Lector BufferedReader = nuevo BufferedReader (nuevo InputStreamReader (entrada));
Cadena nombreLine = lector.readLine();
Cadena ageLine = lector.readLine();
Cadena emailLine= lector.readLine();
Cadena phoneLine= lector.readLine();
Tenga en cuenta que el estado del procesamiento está determinado por cuánto tiempo lleva ejecutándose el programa. En otras palabras, una vez que regresa el método lector.readLine(), usted sabe con certeza que la línea de texto ha sido leída. Es por eso que readline() se bloquea hasta que se haya leído toda la línea. También sabes que esta línea contiene nombres; de manera similar, cuando regresa la segunda llamada readline(), sabes que esta línea contiene edades, etc. Como puede ver, este controlador solo se ejecuta cuando se leen datos nuevos y sabe cuáles son los datos en cada paso. Una vez que un subproceso en ejecución ha procesado algunos de los datos que ha leído, no los revertirá (en su mayoría). La siguiente figura también ilustra este principio:
Leer datos de una transmisión bloqueada
Si bien la implementación de NIO será diferente, aquí hay un ejemplo simple:
Copie el código de código de la siguiente manera:
Búfer ByteBuffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(búfer);
Tenga en cuenta la segunda línea, que lee bytes del canal en un ByteBuffer. Cuando regresa la llamada a este método, no sabe si todos los datos que necesita están en el búfer. Todo lo que sabes es que el búfer contiene algunos bytes, lo que dificulta un poco el procesamiento.
Supongamos que después de la primera llamada de lectura (búfer), los datos leídos en el búfer son solo media línea, por ejemplo, "Nombre: An", ¿puede procesar los datos? Obviamente no, debe esperar hasta que se lea toda la fila de datos en el caché. Antes de eso, cualquier procesamiento de datos no tiene sentido.
Entonces, ¿cómo saber si el búfer contiene suficientes datos para procesar? Bueno, no lo sabes. Los métodos descubiertos solo pueden ver datos en el búfer. El resultado es que debe verificar los datos del búfer varias veces antes de saber que todos los datos están en el búfer. Esto no sólo es ineficiente, sino que también puede saturar la solución de programación. Por ejemplo:
Copie el código de código de la siguiente manera:
Búfer ByteBuffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(búfer);
while(! bufferFull(bytesRead) ) {
bytesRead = inChannel.read(búfer);
}
El método bufferFull() debe realizar un seguimiento de la cantidad de datos leídos en el búfer y devolver verdadero o falso, dependiendo de si el búfer está lleno. En otras palabras, si el búfer está listo para ser procesado, el búfer está lleno.
El método bufferFull() escanea el búfer, pero debe permanecer en el mismo estado que antes de llamar al método bufferFull(). De lo contrario, es posible que los siguientes datos leídos en el búfer no se lean en la ubicación correcta. Esto es imposible, pero es otra cuestión más a tener en cuenta.
Si el buffer está lleno, se puede procesar. Si no funciona y tiene sentido en su caso real, es posible que pueda manejar parte del problema. Pero en muchos casos este no es el caso. La siguiente figura muestra el "ciclo de datos del búfer listo":
Leer datos de un canal hasta que todos los datos se lean en el búfer
Resumir
NIO le permite administrar múltiples canales (conexiones de red o archivos) usando solo un hilo (o varios), pero la desventaja es que analizar los datos puede ser más complejo que leerlos desde un flujo bloqueado.
Si necesita administrar miles de conexiones abiertas simultáneamente que envían solo pequeñas cantidades de datos cada vez, como un servidor de chat, un servidor que implemente NIO puede ser una ventaja. Del mismo modo, si necesita mantener muchas conexiones abiertas con otras computadoras, como en una red P2P, puede ser una ventaja utilizar un hilo separado para administrar todas sus conexiones salientes. El esquema de diseño de múltiples conexiones en un hilo se muestra en la siguiente figura:
Un solo hilo gestiona múltiples conexiones
Si tiene una pequeña cantidad de conexiones que utilizan un ancho de banda muy alto y envían grandes cantidades de datos a la vez, tal vez una implementación típica de servidor IO podría ser una buena opción. La siguiente figura ilustra un diseño típico de servidor IO:
Un diseño típico de servidor IO:
Una conexión es manejada por un hilo.
Canal
Los canales Java NIO son similares a los flujos, pero algo diferentes:
Los datos se pueden leer desde el canal y los datos se pueden escribir en el canal. Pero los flujos de lectura y escritura suelen ser unidireccionales.
Los canales se pueden leer y escribir de forma asincrónica.
Los datos del canal primero deben leerse desde un búfer o siempre escribirse desde un búfer.
Como se mencionó anteriormente, los datos se leen del canal al búfer y los datos se escriben desde el búfer al canal. Como se muestra a continuación:
Implementación del canal
Estas son las implementaciones de los canales más importantes en Java NIO:
FileChannel: lee y escribe datos de archivos.
DatagramChannel: puede leer y escribir datos en la red a través de UDP.
SocketChannel: puede leer y escribir datos en la red a través de TCP.
ServerSocketChannel: puede monitorear las conexiones TCP entrantes, como un servidor web. Se crea un SocketChannel para cada nueva conexión entrante.
Ejemplo de canal básico
El siguiente es un ejemplo del uso de FileChannel para leer datos en un búfer:
Copie el código de código de la siguiente manera:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
mientras (bytesLeer! = -1) {
System.out.println("Leer " + bytesLeer);
buf.flip();
mientras(buf.hasRemaining()){
System.out.print((char) buf.get());
}
buf.clear();
bytesRead = inChannel.read(buf);
}
unArchivo.close();
Tenga en cuenta que la llamada a buf.flip() primero lee los datos en el búfer, luego invierte el búfer y luego lee los datos del búfer. La siguiente sección entrará en más detalles sobre Buffer.
Buffer
El búfer en Java NIO se utiliza para interactuar con los canales NIO. Como sabe, los datos se leen del canal al búfer y se escriben desde el búfer al canal.
Un búfer es esencialmente un bloque de memoria en el que se pueden escribir datos y desde el que luego se pueden leer. Esta memoria está empaquetada como un objeto NIO Buffer y proporciona un conjunto de métodos para acceder cómodamente a esta memoria.
Uso básico de Buffer
El uso de Buffer para leer y escribir datos generalmente sigue los siguientes cuatro pasos:
Escribir datos en el búfer
Llamar al método flip()
Leer datos del búfer
Llame al método clear() o al método compact()
Cuando los datos se escriben en el búfer, el búfer registra cuántos datos se escribieron. Una vez que desee leer datos, debe cambiar el búfer del modo de escritura al modo de lectura mediante el método flip(). En el modo de lectura, se pueden leer todos los datos escritos previamente en el búfer.
Una vez que se han leído todos los datos, es necesario borrar el búfer para poder escribirlo nuevamente. Hay dos formas de borrar el búfer: llamando al método clear() o compact(). El método clear() borra todo el búfer. El método compact() solo borrará los datos que se han leído. Todos los datos no leídos se mueven al principio del búfer y los datos recién escritos se colocan después de los datos no leídos en el búfer.
A continuación se muestra un ejemplo del uso de Buffer:
Copie el código de código de la siguiente manera:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
//crea un buffer con capacidad de 48 bytes
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf); //leer en el búfer.
mientras (bytesLeer! = -1) {
buf.flip();//preparar el buffer para lectura
mientras(buf.hasRemaining()){
System.out.print((char) buf.get()); // lee 1 byte a la vez
}
buf.clear(); //preparar el buffer para escribir
bytesRead = inChannel.read(buf);
}
unArchivo.close();
Capacidad, posición y límite del buffer
Un búfer es esencialmente un bloque de memoria en el que se pueden escribir datos y desde el que luego se pueden leer. Esta memoria está empaquetada como un objeto NIO Buffer y proporciona un conjunto de métodos para acceder cómodamente a esta memoria.
Para comprender cómo funciona Buffer, es necesario estar familiarizado con sus tres propiedades:
capacidad
posición
límite
El significado de posición y límite depende de si el búfer está en modo lectura o escritura. No importa en qué modo se encuentre el Buffer, el significado de capacidad es siempre el mismo.
Aquí hay una explicación de la capacidad, la posición y el límite en el modo de lectura y escritura, con explicaciones detalladas después de la ilustración.
capacidad
Como bloque de memoria, Buffer tiene un valor de tamaño fijo, también llamado "capacidad". Solo puede escribir en él capacidad de byte, long, char y otros tipos. Una vez que el búfer está lleno, es necesario vaciarlo (leyendo o borrando datos) antes de poder continuar escribiendo datos.
posición
Cuando escribe datos en el búfer, la posición representa la posición actual. El valor de la posición inicial es 0. Cuando se escriben datos de bytes, datos largos, etc. en el búfer, la posición avanzará a la siguiente unidad de búfer donde se pueden insertar datos. La posición máxima puede ser capacidad 1.
Cuando se leen datos, también se leen desde una ubicación específica. Al cambiar el búfer del modo de escritura al modo de lectura, la posición se restablecerá a 0. Cuando los datos se leen desde la posición del búfer, la posición avanza a la siguiente posición legible.
límite
En modo de escritura, el límite del búfer indica la cantidad máxima de datos que puede escribir en el búfer. En modo de escritura, el límite es igual a la capacidad del Buffer.
Al cambiar el búfer al modo de lectura, el límite indica la cantidad máxima de datos que puede leer. Por lo tanto, al cambiar el búfer al modo de lectura, el límite se establecerá en el valor de posición en el modo de escritura. En otras palabras, puede leer todos los datos escritos antes (el límite se establece en la cantidad de datos escritos, este valor es la posición en modo de escritura)
Tipo de búfer
Java NIO tiene los siguientes tipos de búfer:
Búfer de bytes
MapeadoByteBuffer
CharBuffer
Doble búfer
búfer flotante
IntBuffer
búfer largo
búfer corto
Como puede ver, estos tipos de búfer representan diferentes tipos de datos. En otras palabras, los bytes en el búfer se pueden manipular mediante tipos char, short, int, long, float o double.
MappedByteBuffer es un poco especial y se discutirá en su propio capítulo.
Asignación de búfer
Para obtener un objeto Buffer, primero debes asignarlo. Cada clase de Buffer tiene un método de asignación. A continuación se muestra un ejemplo de asignación de un ByteBuffer con 48 bytes de capacidad.
Copie el código de código de la siguiente manera:
ByteBuffer buf = ByteBuffer.allocate(48);
Esto asigna un CharBuffer que puede almacenar 1024 caracteres:
Copie el código de código de la siguiente manera:
CharBuffer buf = CharBuffer.allocate(1024);
Escribir datos en el búfer
Hay dos formas de escribir datos en Buffer:
Escribe desde el canal al búfer.
Escriba en Buffer mediante el método put() de Buffer.
Ejemplo de escritura del canal al búfer
Copie el código de código de la siguiente manera:
int bytesRead = inChannel.read(buf); //leer en el búfer.
Ejemplo de escritura de Buffer mediante el método put:
Copie el código de código de la siguiente manera:
buf.put(127);
Hay muchas versiones del método put, que le permiten escribir datos en el Buffer de diferentes maneras. Por ejemplo, escribir en una ubicación específica o escribir una matriz de bytes en un búfer. Para obtener más detalles sobre la implementación del búfer, consulte JavaDoc.
método voltear()
El método flip cambia el búfer del modo de escritura al modo de lectura. Llamar al método flip() restablecerá la posición a 0 y establecerá el límite al valor de la posición anterior.
En otras palabras, la posición ahora se usa para marcar la posición de lectura y el límite representa cuántos bytes, caracteres, etc. se escribieron antes; cuántos bytes, caracteres, etc. se pueden leer ahora.
Leer datos del búfer
Hay dos formas de leer datos del Buffer:
Leer datos del búfer al canal.
Utilice el método get() para leer datos del búfer.
Ejemplo de lectura de datos del búfer al canal:
Copie el código de código de la siguiente manera:
//leer del búfer al canal.
int bytesWritten = inChannel.write(buf);
Ejemplo de uso del método get() para leer datos del Buffer
Copie el código de código de la siguiente manera:
byte aByte = buf.get();
Existen muchas versiones del método get, que le permiten leer datos del Buffer de diferentes maneras. Por ejemplo, leer desde una posición específica o leer datos de un búfer en una matriz de bytes. Para obtener más detalles sobre la implementación del búfer, consulte JavaDoc.
método rebobinar()
Buffer.rewind() vuelve a establecer la posición en 0, para que puedas volver a leer todos los datos en el Buffer. El límite permanece sin cambios y aún indica cuántos elementos (bytes, caracteres, etc.) se pueden leer desde el búfer.
métodos clear() y compact()
Una vez que se han leído los datos del búfer, el búfer debe estar listo para poder escribirse nuevamente. Esto se puede hacer mediante los métodos clear() o compact().
Si se llama al método clear(), la posición volverá a ser 0 y el límite se establecerá en el valor de la capacidad. En otras palabras, se borra el búfer. Los datos del búfer no se borran, pero estas marcas nos indican dónde empezar a escribir datos en el búfer.
Si hay algunos datos no leídos en el búfer y llama al método clear(), los datos se "olvidarán", lo que significa que ya no habrá marcadores que le indiquen qué datos se han leído y cuáles no.
Si todavía hay datos sin leer en el búfer y los datos se necesitan más adelante, pero primero desea escribir algunos datos, utilice el método compact().
El método compact() copia todos los datos no leídos al principio del Buffer. Luego establezca la posición justo detrás del último elemento no leído. El atributo de límite todavía está configurado en capacidad como el método clear(). El búfer ahora está listo para escribir datos, pero los datos no leídos no se sobrescribirán.
métodos mark() y reset()
Al llamar al método Buffer.mark(), puede marcar una posición específica en el Buffer. Posteriormente podrá restaurar a esta posición llamando al método Buffer.reset(). Por ejemplo:
Copie el código de código de la siguiente manera:
buffer.marca();
//llamar a buffer.get() un par de veces, por ejemplo, durante el análisis.
buffer.reset();//establecer la posición nuevamente a la marca.
métodos iguales() y compararTo()
Puede utilizar los métodos equals() y compareTo() para dos buffers.
es igual()
Cuando se cumplen las siguientes condiciones, significa que los dos Buffers son iguales:
Tener el mismo tipo (byte, char, int, etc.).
El número de bytes, caracteres, etc. restantes en el búfer es igual.
Todos los bytes, caracteres, etc. restantes en el búfer son iguales.
Como puede ver, igual solo compara parte del búfer, no todos los elementos que contiene. De hecho, solo compara los elementos restantes en el Buffer.
método comparar con ()
El método compareTo() compara los elementos restantes (byte, char, etc.) de dos Buffers. Si se cumplen las siguientes condiciones, un Buffer se considera "menor que" el otro Buffer.
El primer elemento desigual es más pequeño que el elemento correspondiente en el otro búfer.
Todos los elementos son iguales, pero el primer Buffer se agota antes que el otro (el primer Buffer tiene menos elementos que el otro).
(Anotación: Los elementos restantes son los elementos desde la posición hasta el límite)
Dispersar/Reunir
(Dirección original de esta parte, autor: Jakob Jenkov, traductor: Guo Lei)
Java NIO comienza a admitir scatter/gather. Scatter/gather se utiliza para describir la operación de lectura o escritura en el canal (Nota del traductor: Canal a menudo se traduce como canal en chino).
La lectura dispersa del canal significa escribir los datos leídos en múltiples buffers durante la operación de lectura. Por lo tanto, el Canal "dispersa" los datos leídos del Canal en múltiples Buffers.
Recopilar y escribir en un canal significa escribir datos de múltiples buffers en el mismo canal durante una operación de escritura. Por lo tanto, el canal "reúne" los datos en múltiples buffers y los envía al canal.
La dispersión/reunión se utiliza a menudo en situaciones en las que los datos transmitidos deben procesarse por separado. Por ejemplo, cuando se transmite un mensaje que consta de un encabezado y un cuerpo del mensaje, puede dispersar el cuerpo y el encabezado del mensaje en diferentes buffers, de modo que. Puede procesar cómodamente los encabezados y cuerpos de los mensajes.
Lecturas dispersas
Las lecturas dispersas se refieren a la lectura de datos de un canal en múltiples buffers. Como se describe en la siguiente figura:
El ejemplo de código es el siguiente:
Copie el código de código de la siguiente manera:
Encabezado ByteBuffer = ByteBuffer.allocate(128);
Cuerpo de ByteBuffer = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = {encabezado, cuerpo};
canal.read(bufferArray);
Tenga en cuenta que el búfer se inserta primero en la matriz y luego la matriz se usa como parámetro de entrada para canal.read(). El método read() escribe los datos leídos del canal en el búfer en el orden del búfer en la matriz. Cuando se llena un búfer, el canal escribe en otro búfer.
Las lecturas dispersas deben llenar el búfer actual antes de pasar al siguiente búfer, lo que también significa que no es adecuado para mensajes dinámicos (Nota del traductor: el tamaño del mensaje no es fijo). En otras palabras, si hay un encabezado y un cuerpo de mensaje, el encabezado del mensaje debe estar completamente lleno (por ejemplo, 128 bytes) para que las lecturas dispersas funcionen correctamente.
Recopilación de escrituras
Recopilar escrituras significa que los datos se escriben desde múltiples buffers en el mismo canal. Como se describe en la siguiente figura:
El ejemplo de código es el siguiente:
Copie el código de código de la siguiente manera:
Encabezado ByteBuffer = ByteBuffer.allocate(128);
Cuerpo de ByteBuffer = ByteBuffer.allocate(1024);
//escribir datos en buffers
ByteBuffer[] bufferArray = {encabezado, cuerpo};
canal.write(bufferArray);
La matriz de buffers es el parámetro de entrada del método write(). El método write() escribirá datos en el canal en el orden de los buffers en la matriz. Tenga en cuenta que solo se escribirán los datos entre la posición y el límite. Por lo tanto, si un búfer tiene una capacidad de 128 bytes pero solo contiene 58 bytes de datos, entonces los 58 bytes de datos se escribirán en el canal. Por lo tanto, a diferencia de Scattering Reads, Gathering Writes puede manejar mejor los mensajes dinámicos.
Transferencia de datos entre canales.
(Dirección original de esta parte, autor: Jakob Jenkov, traductor: Guo Lei, corrector: Zhou Tai)
En Java NIO, si uno de los dos canales es un FileChannel, entonces puede transferir datos directamente desde un canal (Nota del traductor: canal a menudo se traduce como canal en chino) a otro canal.
transferirDesde()
El método transferFrom() de FileChannel puede transferir datos desde el canal de origen al FileChannel (Nota del traductor: este método se explica en la documentación del JDK como transferir bytes de un canal de bytes legible determinado al archivo de este canal). He aquí un ejemplo sencillo:
Copie el código de código de la siguiente manera:
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
posición larga = 0;
cuenta larga = fromChannel.size();
toChannel.transferFrom(posición, recuento, fromChannel);
La posición del parámetro de entrada del método indica a partir de la posición para escribir datos en el archivo de destino, y el recuento indica el número máximo de bytes transferidos. Si el canal de origen tiene menos de bytes de espacio restante, la cantidad de bytes transferidos es menor que la cantidad de bytes solicitados.
Además, cabe señalar que en la implementación de SoketChannel, SocketChannel solo transmitirá los datos preparados en este momento (que pueden tener menos de bytes). Por lo tanto, es posible que SocketChannel no transfiera todos los datos solicitados (contar bytes) al FileChannel.
transferir a()
El método transferTo() transfiere datos de FileChannel a otros canales. He aquí un ejemplo sencillo:
Copie el código de código de la siguiente manera:
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
posición larga = 0;
cuenta larga = fromChannel.size();
fromChannel.transferTo(posición, recuento, toChannel);
¿Encontraste que este ejemplo es particularmente similar al anterior? Excepto que el objeto FileChannel que llama al método es diferente, todo lo demás es igual.
Los problemas mencionados anteriormente sobre SocketChannel también existen en el método transferTo(). SocketChannel continuará transmitiendo datos hasta que se llene el búfer de destino.
Selector
(Enlace al texto original de esta sección, autor: Jakob Jenkov, traductor: Langjiv, corrector: Ding Yi)
Selector es un componente en Java NIO que puede detectar uno o más canales NIO y saber si el canal está listo para eventos como lectura y escritura. De esta manera, un único hilo puede gestionar múltiples canales y, por tanto, múltiples conexiones de red.
(1) ¿Por qué utilizar Selector?
La ventaja de utilizar un solo subproceso para manejar múltiples canales es que se necesitan menos subprocesos para manejar los canales. De hecho, es posible utilizar un solo hilo para manejar todos los canales. Para el sistema operativo, el cambio de contexto entre subprocesos es muy costoso y cada subproceso ocupa algunos recursos del sistema (como la memoria). Por lo tanto, cuantos menos hilos se utilicen, mejor.
Sin embargo, tenga en cuenta que los sistemas operativos y las CPU modernos son cada vez mejores en la multitarea, por lo que la sobrecarga del multiproceso se vuelve cada vez menor con el tiempo. De hecho, si una CPU tiene varios núcleos, no utilizar la multitarea puede ser un desperdicio de energía de la CPU. De todos modos, la discusión sobre ese diseño debería estar en un artículo diferente. Aquí basta con saber que puedes manejar múltiples canales usando Selector.
El siguiente es un diagrama de ejemplo de un solo subproceso que utiliza un selector para procesar tres canales:
(2)Creación de Selector
Cree un Selector llamando al método Selector.open(), de la siguiente manera:
Copie el código de código de la siguiente manera:
Selector selector = Selector.open();
(3) Registre el canal con Selector
Para utilizar Canal y Selector juntos, el canal debe estar registrado con el selector. Esto se logra mediante el método SelectableChannel.register(), de la siguiente manera:
Copie el código de código de la siguiente manera:
canal.configureBlocking (falso);
Tecla SelectionKey = canal.registro(selector,
Tecla de selección.OP_READ);
Cuando se usa con un Selector, el Canal debe estar en modo sin bloqueo. Esto significa que no puede usar FileChannel con un Selector porque FileChannel no se puede cambiar al modo sin bloqueo. Los canales de enchufe están bien.
Tenga en cuenta el segundo parámetro del método Register(). Esta es una "colección de intereses", lo que significa qué eventos le interesan cuando escucha el canal a través del Selector. Hay cuatro tipos diferentes de eventos que se pueden escuchar:
Conectar
Aceptar
Leer
Escribir
Un canal que activa un evento significa que el evento está listo. Por lo tanto, un canal que se conecta exitosamente a otro servidor se denomina "listo para conectarse". Se dice que un canal de socket de servidor está "listo para recibir" cuando está listo para recibir conexiones entrantes. Un canal que tiene datos para leer se dice que está "listo para leer". Se puede decir que un canal que espera escribir datos está "listo para escribir".
Estos cuatro eventos están representados por las cuatro constantes de SelectionKey:
Clave de selección.OP_CONNECT
Clave de selección.OP_ACCEPT
Clave de selección.OP_READ
Clave de selección.OP_WRITE
Si está interesado en más de un evento, puede utilizar el operador OR bit a bit para conectar las constantes, de la siguiente manera:
Copie el código de código de la siguiente manera:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
Las recaudaciones de intereses se mencionarán a continuación.
(4) Tecla de selección
En la sección anterior, al registrar un Canal con el Selector, el método Register() devuelve un objeto SelectionKey. Este objeto contiene algunas propiedades que pueden ser de su interés:
cobro de intereses
colección lista
Canal
Selector
Objetos adicionales (opcional)
A continuación describo estas propiedades.
cobro de intereses
Como se describe en la sección Registrar un canal con un selector, la colección de intereses es una colección de eventos interesantes que usted selecciona. Puede leer y escribir la colección de intereses a través de SelectionKey, así:
Copie el código de código de la siguiente manera:
int interestSet = selecciónClave.interess();
booleano isInterestedInAccept= (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
booleano isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
booleano isInterestedInRead = interestSet & SelectionKey.OP_READ;
booleano isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
Se puede ver que al utilizar "bit AND" para operar el conjunto de intereses y la constante SelectionKey dada, se puede determinar si un determinado evento está en el conjunto de intereses.
colección lista
El conjunto listo es el conjunto de operaciones para las cuales el canal está listo. Después de una selección (Selección), accederá primero al conjunto listo. La selección se explicará en la siguiente sección. Se puede acceder a la colección lista de esta manera:
int readySet = selecciónKey.readyOps();
Puede utilizar el mismo método que detecta el cobro de intereses para detectar qué eventos u operaciones están listos en el canal. Sin embargo, también están disponibles los siguientes cuatro métodos, todos los cuales devuelven un tipo booleano:
Copie el código de código de la siguiente manera:
selecciónKey.isAcceptable();
selecciónKey.isConnectable();
selecciónKey.isReadable();
selecciónKey.isWritable();
Selector de canal+
Acceder al canal y al selector desde SelectionKey es sencillo. como sigue:
Copie el código de código de la siguiente manera:
Canalcanal= clave de selección.canal();
Selector selector = selecciónKey.selector();
objetos adicionales
Se puede adjuntar un objeto o más información a SelectionKey para identificar fácilmente un canal determinado. Por ejemplo, puede adjuntar un búfer para usarlo con un canal o un objeto que contenga datos agregados. Cómo usarlo:
Copie el código de código de la siguiente manera:
selecciónClave.attach(elObjeto);
Objeto adjuntoObj = selecciónKey.attachment();
También puede adjuntar objetos al registrar el Canal con el Selector usando el método Register(). como:
Copie el código de código de la siguiente manera:
Tecla SelectionKey = canal.register(selector, SelectionKey.OP_READ, theObject);
(5) Seleccione el canal a través del Selector
Una vez que uno o más canales están registrados con un Selector, se pueden llamar varios métodos select() sobrecargados. Estos métodos devuelven aquellos canales que están listos para el evento que le interesa (como conectar, aceptar, leer o escribir). En otras palabras, si está interesado en canales "listos para lectura", el método select() devolverá aquellos canales para los cuales los eventos de lectura están listos.
Aquí está el método select():
int seleccionar()
int select (tiempo de espera largo)
int seleccionarAhora()
select() se bloquea hasta que al menos un canal esté listo para el evento que registró.
select(tiempo de espera prolongado) es lo mismo que select(), excepto que se bloqueará durante un tiempo de espera de hasta milisegundos (parámetro).
selectNow() no bloquea y regresa inmediatamente sin importar qué canal esté listo (Nota del traductor: este método realiza una operación de selección sin bloqueo. Si ningún canal se puede seleccionar desde la operación de selección anterior, este método devuelve cero directamente).
El valor int devuelto por el método select() indica cuántos canales están listos. Es decir, cuántos canales están listos desde la última llamada al método select(). Si se llama al método select(), se devuelve 1 porque un canal está listo. Si se llama nuevamente al método select(), si otro canal está listo, devolverá 1 nuevamente. Si no se realizan operaciones en el primer canal listo, ahora hay dos canales listos, pero entre cada llamada al método select(), solo un canal está listo.
claves seleccionadas()
Una vez que se llama al método select() y el valor de retorno indica que uno o más canales están listos, se puede acceder a los canales listos en el "conjunto de claves seleccionadas" llamando al método selectedKeys() del selector. Como se muestra a continuación:
Copie el código de código de la siguiente manera:
Establecer claves seleccionadas = selector.selectedKeys();
Al registrar un canal como un selector, el método Channel.register() devuelve un objeto SelectionKey. Este objeto representa el canal registrado en el Selector. Se puede acceder a estos objetos a través del método selectedKeySet() de SelectionKey.
Se puede acceder a los canales listos atravesando este conjunto de teclas seleccionado. como sigue:
Copie el código de código de la siguiente manera:
Establecer claves seleccionadas = selector.selectedKeys();
Iterador keyIterator = selectedKeys.iterator();
mientras(keyIterator.hasNext()) {
Tecla SelectionKey = keyIterator.next();
si(clave.isAcceptable()) {
// una conexión fue aceptada por ServerSocketChannel.
} más si (key.isConnectable()) {
// se estableció una conexión con un servidor remoto.
} más si (key.isReadable()) {
// un canal está listo para leer
} si no (key.isWritable()) {
// un canal está listo para escribir
}
keyIterator.<tuihighlight><a href="javascript:;" style="display:inline;float:none;position:inherit;cursor:pointer;color:#7962D5;text-decoration:underline;">eliminar</a ></tuihighlight>();
}
Este bucle recorre cada clave en el conjunto de claves seleccionado y detecta el evento listo para el canal correspondiente a cada clave.
Tenga en cuenta la llamada keyIterator.remove() al final de cada iteración. El Selector no elimina instancias de SelectionKey del conjunto de claves seleccionado. Debe eliminarlo usted mismo cuando se procese el canal. La próxima vez que el canal esté listo, el Selector lo colocará nuevamente en el conjunto de claves seleccionado.
El canal devuelto por el método SelectionKey.channel() debe convertirse al tipo que desea procesar, como ServerSocketChannel o SocketChannel, etc.
(6) despertar()
Un hilo se bloquea después de llamar al método select() Incluso si no hay ningún canal listo, hay una manera de devolverlo desde el método select(). Simplemente deje que otros subprocesos llamen al método Selector.wakeup() en el objeto donde el primer subproceso llamó al método select(). El hilo bloqueado en el método select() regresará inmediatamente.
Si otro hilo llama al método wakeup(), pero actualmente no hay ningún hilo bloqueado en el método select(), el siguiente hilo que llame al método select() se "despertará" inmediatamente.
(7)cerrar()
Llamar a su método close() después de usar el Selector cerrará el Selector e invalidará todas las instancias de SelectionKey registradas en el Selector. El canal en sí no se cierra.
(8) Ejemplo completo
Aquí hay un ejemplo completo: abra un Selector, registre un canal en el Selector (se omite el proceso de inicialización del canal) y luego monitoree continuamente si los cuatro eventos del Selector (aceptar, conectar, leer, escribir) están listos.
Copie el código de código de la siguiente manera:
Selector selector = Selector.open();
canal.configureBlocking (falso);
Tecla SelectionKey = canal.register(selector, SelectionKey.OP_READ);
mientras (verdadero) {
int readyChannels = selector.select();
if(readyChannels == 0) continuar;
Establecer claves seleccionadas = selector.selectedKeys();
Iterador keyIterator = selectedKeys.iterator();
mientras(keyIterator.hasNext()) {
Tecla SelectionKey = keyIterator.next();
si(clave.isAcceptable()) {
// una conexión fue aceptada por ServerSocketChannel.
} más si (key.isConnectable()) {
// se estableció una conexión con un servidor remoto.
} más si (key.isReadable()) {
// un canal está listo para leer
} si no (key.isWritable()) {
// un canal está listo para escribir
}
keyIterator.<tuihighlight><a href="javascript:;" style="display:inline;float:none;position:inherit;cursor:pointer;color:#7962D5;text-decoration:underline;">eliminar</a ></tuihighlight>();
}
}
canal de archivos
(Enlace al texto original de esta sección, autor: Jakob Jenkov, traductor: Zhou Tai, revisor: Ding Yi)
FileChannel en Java NIO es un canal conectado a un archivo. Los archivos se pueden leer y escribir a través de canales de archivos.
FileChannel no se puede configurar en modo sin bloqueo, siempre se ejecuta en modo de bloqueo.
Canal de archivo abierto
Antes de utilizar FileChannel, se debe abrir. Sin embargo, no podemos abrir un FileChannel directamente. Necesitamos obtener una instancia de FileChannel utilizando un InputStream, OutputStream o RandomAccessFile. A continuación se muestra un ejemplo de cómo abrir un FileChannel a través de RandomAccessFile:
Copie el código de código de la siguiente manera:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
Leer datos de FileChannel
Llame a uno de los múltiples métodos read() para leer datos del FileChannel. como:
Copie el código de código de la siguiente manera:
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
Primero, asigne un búfer. Los datos leídos desde FileChannel se leerán en Buffer.
Luego, llame al método FileChannel.read(). Este método lee datos de FileChannel en Buffer. El valor int devuelto por el método read() indica cuántos bytes se leyeron en el búfer. Si devuelve -1, significa que se ha llegado al final del archivo.
Escribir datos en FileChannel
Utilice el método FileChannel.write() para escribir datos en FileChannel. El parámetro de este método es un búfer. como:
Copie el código de código de la siguiente manera:
String newData = "Nueva cadena para escribir en el archivo..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
canal.write(buf);
}
Tenga en cuenta que FileChannel.write() se llama en un bucle while. Debido a que no hay garantía de cuántos bytes el método write() puede escribir en FileChannel a la vez, es necesario llamar al método write() repetidamente hasta que no queden bytes en el búfer que no se hayan escrito en el canal.
Cerrar canal de archivo
El FileChannel debe cerrarse cuando haya terminado con él. como:
Copie el código de código de la siguiente manera:
canal.cerrar();
Método de posición de FileChannel
A veces puede ser necesario leer/escribir datos en una ubicación específica en FileChannel. Puede obtener la posición actual de FileChannel llamando al método position().
También puede establecer la posición actual de FileChannel llamando al método de posición (pos larga).
Aquí hay dos ejemplos:
Copie el código de código de la siguiente manera:
pos largo = canal.posición();
posición.canal(pos +123);
Si establece la posición después del final del archivo y luego intenta leer datos del canal del archivo, el método de lectura devolverá -1, el indicador de fin del archivo.
Si establece la posición después del final del archivo y luego escribe datos en el canal, el archivo se expandirá a la posición actual y se escribirán los datos. Esto puede provocar "agujeros en los archivos", espacios entre los datos escritos en los archivos físicos del disco.
Método de tamaño de FileChannel
El método size() de una instancia de FileChannel devolverá el tamaño del archivo asociado con la instancia. como:
Copie el código de código de la siguiente manera:
tamaño de archivo largo = canal.tamaño();
Método truncado de FileChannel
Puede utilizar el método FileChannel.truncate() para interceptar un archivo. Al interceptar un archivo, se eliminará la parte posterior a la longitud especificada del archivo. como:
Copie el código de código de la siguiente manera:
canal.truncar(1024);
Este ejemplo intercepta los primeros 1024 bytes del archivo.
Método de fuerza de FileChannel
El método FileChannel.force() fuerza los datos en el canal que aún no se han escrito en el disco. Por motivos de rendimiento, el sistema operativo almacena los datos en la memoria caché, por lo que no hay garantía de que los datos escritos en FileChannel se escriban en el disco inmediatamente. Para garantizar esto, es necesario llamar al método force().
El método force() tiene un parámetro booleano que indica si se deben escribir metadatos del archivo (información de permisos, etc.) en el disco al mismo tiempo.
El siguiente ejemplo fuerza tanto los datos del archivo como los metadatos al disco:
Copie el código de código de la siguiente manera:
canal.force(verdadero);
Canal de enchufe
(Enlace al texto original de esta sección, autor: Jakob Jenkov, traductor: Zheng Yuting, corrector: Ding Yi)
SocketChannel en Java NIO es un canal conectado a un socket de red TCP. SocketChannel se puede crear de las siguientes 2 maneras:
Abra un SocketChannel y conéctese a un servidor en Internet.
Cuando llega una nueva conexión a ServerSocketChannel, se crea un SocketChannel.
Abrir canal de socket
A continuación se explica cómo abrir SocketChannel:
Copie el código de código de la siguiente manera:
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
Cerrar canal de socket
Cuando haya terminado con SocketChannel, llame a SocketChannel.close() para cerrar SocketChannel:
Copie el código de código de la siguiente manera:
socketChannel.close();
Leer datos de SocketChannel
Para leer datos de un SocketChannel, llame a uno de los métodos read(). Aquí hay ejemplos:
Copie el código de código de la siguiente manera:
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);
Primero, asigne un búfer. Los datos leídos de SocketChannel se colocarán en este búfer.
Luego, llame a SocketChannel.read(). Este método lee datos de SocketChannel en Buffer. El valor int devuelto por el método read() indica cuántos bytes se leyeron en el búfer. Si se devuelve -1, significa que se ha leído el final de la transmisión (la conexión se cerró).
Escribir en SocketChannel
Escribir datos en SocketChannel utiliza el método SocketChannel.write(), que toma un Buffer como parámetro. Los ejemplos son los siguientes:
Copie el código de código de la siguiente manera:
String newData = "Nueva cadena para escribir en el archivo..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
canal.write(buf);
}
Tenga en cuenta que el método SocketChannel.write() se llama en un bucle while. El método Write() no puede garantizar cuántos bytes se pueden escribir en SocketChannel. Entonces, llamamos a write() repetidamente hasta que al búfer no le quedan bytes para escribir.
modo sin bloqueo
Puede configurar SocketChannel en modo sin bloqueo. Después de la configuración, puede llamar a connect(), read() y write() en modo asincrónico.
conectar()
Si SocketChannel está en modo sin bloqueo y se llama a connect() en este momento, el método puede regresar antes de que se establezca la conexión. Para determinar si la conexión está establecida, puede llamar al método FinishConnect(). Como esto:
Copie el código de código de la siguiente manera:
socketChannel.configureBlocking (falso);
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
mientras(! socketChannel.finishConnect() ){
//espera, o haz otra cosa...
}
escribir()
En modo sin bloqueo, el método write() puede regresar antes de escribir nada. Por lo tanto, es necesario llamar a write() en el bucle. Ha habido ejemplos antes, por lo que no entraré en detalles aquí.
leer()
En el modo sin bloqueo, el método read() puede regresar antes de que se hayan leído los datos. Por lo tanto, debe prestar atención a su valor de retorno int, que le indicará cuántos bytes se leyeron.
Modo sin bloqueo y selectores.
El modo sin bloqueo funciona mejor con selectores. Al registrar uno o más SocketChannels con el Selector, puede preguntarle al selector qué canal está listo para leer, escribir, etc. La combinación de Selector y SocketChannel se discutirá en detalle más adelante.
Canal ServerSocket
(Enlace al texto original de esta sección, autor: Jakob Jenkov, traductor: Zheng Yuting, corrector: Ding Yi)
ServerSocketChannel en Java NIO es un canal que puede escuchar nuevas conexiones TCP entrantes, al igual que ServerSocket en IO estándar. La clase ServerSocketChannel está en el paquete java.nio.channels.
Aquí hay un ejemplo:
Copie el código de código de la siguiente manera:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
mientras (verdadero) {
SocketChannel socketChannel =
serverSocketChannel.accept();
//hacer algo con socketChannel...
}
Abrir ServerSocketChannel
Abra ServerSocketChannel llamando al método ServerSocketChannel.open(). Por ejemplo:
Copie el código de código de la siguiente manera:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
Cerrar ServerSocketChannel
Cierre ServerSocketChannel llamando al método ServerSocketChannel.close() Por ejemplo:
Copie el código de código de la siguiente manera:
serverSocketChannel.close();
Escuche nuevas conexiones entrantes
Escuche nuevas conexiones entrantes a través del método ServerSocketChannel.accept(). Cuando regresa el método aceptar(), devuelve un SocketChannel que contiene la nueva conexión entrante. Por lo tanto, el método aceptar() se bloqueará hasta que llegue una nueva conexión.
Por lo general, en lugar de simplemente escuchar una conexión, se llama al método aceptar() en el bucle while como en el siguiente ejemplo:
Copie el código de código de la siguiente manera:
mientras (verdadero) {
SocketChannel socketChannel =
serverSocketChannel.accept();
//hacer algo con socketChannel...
}
Por supuesto, también puedes usar otros criterios de salida además de verdadero en el ciclo while.
modo sin bloqueo
ServerSocketChannel se puede configurar en modo sin bloqueo. En modo sin bloqueo, el método aceptar () regresará inmediatamente. Si no hay una nueva conexión entrante, el valor de retorno será nulo. Por lo tanto, debe verificar si el SocketChannel devuelto es nulo. como:
Copie el código de código de la siguiente manera:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking (falso);
mientras (verdadero) {
SocketChannel socketChannel =
serverSocketChannel.accept();
si(canalsocket!= nulo){
//hacer algo con socketChannel...
}
}
canal de datagrama
(Enlace al texto original de esta sección, autor: Jakob Jenkov, traductor: Zheng Yuting, corrector: Ding Yi)
DatagramChannel en Java NIO es un canal que puede enviar y recibir paquetes UDP. Debido a que UDP es un protocolo de red sin conexión, no se puede leer ni escribir como otros canales. Envía y recibe paquetes de datos.
Canal OpenDatagram
Así es como se abre DatagramChannel:
Copie el código de código de la siguiente manera:
Canal DatagramChannel = DatagramChannel.open();
canal.socket().bind(new InetSocketAddress(9999));
El DatagramChannel abierto en este ejemplo puede recibir paquetes en el puerto UDP 9999.
recibir datos
Reciba datos de DatagramChannel a través del método recibir (), como por ejemplo:
Copie el código de código de la siguiente manera:
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
canal.recibir(buf);
El método de recepción () copiará el contenido del paquete de datos recibido en el búfer especificado. Si el búfer no puede acomodar los datos recibidos, los datos sobrantes se descartarán.
enviar datos
Envíe datos desde DatagramChannel a través del método send(), como por ejemplo:
Copie el código de código de la siguiente manera:
String newData = "Nueva cadena para escribir en el archivo..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
int bytesEnviado = canal.enviar(buf, nueva InetSocketAddress("jenkov.com", 80));
Este ejemplo envía una cadena de caracteres al puerto UDP 80 del servidor "jenkov.com". Como el servidor no monitorea este puerto, no sucederá nada. Tampoco le notificará si se ha recibido el paquete saliente, porque UDP no tiene ninguna garantía en términos de entrega de datos.
Conectarse a una dirección específica
Un DatagramChannel se puede "conectar" a una dirección específica en la red. Dado que UDP no tiene conexión, conectarse a una dirección específica no crea una conexión real como un canal TCP. En cambio, DatagramChannel está bloqueado para que solo pueda enviar y recibir datos desde una dirección específica.
Aquí hay un ejemplo:
Copie el código de código de la siguiente manera:
canal.connect(new InetSocketAddress("jenkov.com", 80));
Una vez conectado, también puedes usar los métodos read() y write() tal como lo harías con un canal tradicional. Simplemente no hay garantías con respecto a la transferencia de datos. A continuación se muestran algunos ejemplos:
Copie el código de código de la siguiente manera:
int bytesRead = canal.read(buf);
int bytesWritten = canal.escribir(pero);
Tubo
(Enlace al texto original de esta sección, autor: Jakob Jenkov, traductor: Huang Zhong, corrector: Ding Yi)
Una tubería Java NIO es una conexión de datos unidireccional entre 2 subprocesos. La tubería tiene un canal fuente y un canal sumidero. Los datos se escribirán en el canal receptor y se leerán desde el canal fuente.
A continuación se muestra una ilustración del principio de tubería:
Crear canalización
Abra la tubería mediante el método Pipe.open(). Por ejemplo:
Copie el código de código de la siguiente manera:
Tubería tubería = Pipe.open();
Escribir datos en la tubería.
Para escribir datos en la tubería, debe acceder al canal receptor. Como esto:
Copie el código de código de la siguiente manera:
Pipe.SinkChannel fregaderoChannel = pipe.sink();
Escriba datos en SinkChannel llamando al método write() de SinkChannel, así:
Copie el código de código de la siguiente manera:
String newData = "Nueva cadena para escribir en el archivo..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
<b>sinkChannel.write(buf);</b>
}
[código]
Leer datos de la tubería
Para leer datos de una tubería, debe acceder al canal de origen, así:
[código]
Pipe.SourceChannel sourceChannel = pipe.source();
Llame al método read() del canal de origen para leer los datos, así:
Copie el código de código de la siguiente manera:
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
El valor int devuelto por el método read() nos dirá cuántos bytes se leyeron en el búfer.