El siguiente sistema proporciona una introducción en profundidad al mecanismo de función de Java Nio y el uso a través de principios, procesos, etc., aprendamos.
Prefacio
Este artículo explica principalmente el mecanismo IO en Java
Dividido en dos piezas:
La primera parte explica el mecanismo IO bajo múltiples lectura. La segunda parte explica cómo optimizar el desperdicio de recursos de CPU bajo el mecanismo IO (nuevo IO)
Servidor de eco
No necesito introducir el mecanismo de enchufe en un solo hilo. Si no lo sabe, puede verificar la información en tantos hilos. ¿Qué pasa si usas enchufes?
Utilizamos el servidor de eco más simple para ayudar a todos a comprender
Primero, echemos un vistazo al diagrama de flujo de trabajo del servidor y el cliente en múltiples subprocesos:
Puede ver que varios clientes envían solicitudes al servidor al mismo tiempo
El servidor ha hecho una medida para habilitar múltiples subprocesos para que coincidan con el cliente correspondiente.
Y cada hilo completa solo sus solicitudes de clientes
Después de que termine el principio, veamos cómo se implementa
Aquí escribí un servidor simple
Use la tecnología de grupo de subprocesos para crear subprocesos (he comentado en la función de código específico):
clase pública myServer {private static ejecutorservice ejecutorservice = ejecutors.newCachedThreadPool (); // Crear una clase de hilo de hilo PRIVADA STICATIC HANDLEMSG Implements Runnable {// Una vez que hay una nueva solicitud del cliente, cree este hilo para procesar el cliente Socket; // Crear un cliente público HandlemSg (Socket Client) {// Construye un parámetro que vincule this.client = client; } @Override public void run () {bufferedReader bufferedReader = null; // Crear una entrada de caché de caracteres Stream PrintWriter printWriter = null; // Crear transmisión de escritura de caracteres Try {BufferedReader = new BufferedReader (new InputStreamReader (Client.getInputStream ())); // Obtener la transmisión de entrada del cliente printWriter = new PrintWriter (Client.getOutputStream (), true); // Obtener la secuencia de salida del cliente, verdadero es actualizar la cadena inputline = null; largo a = System.CurrentTimemillis (); while ((inputline = bufferedReader.readline ())! = null) {printwriter.println (inputline); } largo B = System.CurrentTimemillis (); System.out.println ("Este hilo tomó:"+(ba)+"segundos!"); } catch (ioException e) {E.PrintStackTrace (); } finalmente {try {bufferedReader.close (); printWriter.close (); client.close (); } catch (ioException e) {E.PrintStackTrace (); }}}} public static void main (string [] args) lanza IOException {// El hilo principal del servidor se usa para escuchar las solicitudes del cliente en un servidor de bucle Serversocket = new Serversocket (8686); // Crear un servidor con el puerto 8686 Socket Client = NULL; while (true) {// bucle escuchar client = server.accept (); // El servidor escucha un sistema de solicitud de cliente.out.println (client.getRemotesocketAddress ()+"¡La conexión del cliente es exitosa!"); EjecutorService.submit (new HandlemSg (Cliente)); // Ponga la solicitud del cliente en el hilo HandLMSG a través del grupo de subprocesos para procesar}}}En el código anterior, usamos una clase para escribir un servidor Echo simple para habilitar la escucha de puertos en el hilo principal usando un bucle muerto.
Cliente simple
Con un servidor, podemos acceder a él y enviar algunos datos de cadena. La función del servidor es devolver estas cadenas e imprimir el hilo que toma el tiempo.
Escribamos un cliente simple para responder al servidor:
public class myclient {public static void main (string [] args) lanza ioexception {socket client = null; PrintWriter printWriter = null; BufferedReader BufferedReader = NULL; intente {client = new Socket (); client.connect (nuevo inetSocketAddress ("localhost", 8686)); printWriter = new PrintWriter (Client.getOutputStream (), true); printwriter.println ("hola"); printwriter.flush (); bufferedReader = new BufferedReader (new InputStreamReader (Client.getInputStream ())); // Lea la información devuelta por el servidor y el sistema de salida.out.println ("La información del servidor es:"+BufferedReader.Readline ()); } catch (ioException e) {E.PrintStackTrace (); } finalmente {printWriter.close (); BufferedReader.close (); client.close (); }}}En el código, usamos la secuencia de caracteres para enviar una cadena de saludo. Si el código está bien, el servidor devolverá los datos de Hello e imprimirá la información del registro que establecemos.
Pantalla de resultados del servidor de eco
Vamos a correr:
1. Abra el servidor y habilite la escucha de bucle:
2. Abra un cliente:
Puede ver que el cliente imprime el resultado de retorno
3. Verifique el registro del servidor:
Muy bueno, se implementa una simple programación de sockets multiproceso
Pero piénsalo:
Si un cliente solicita, agregue el sueño durante la escritura IO al servidor,
Hacer que cada solicitud ocupe el hilo del servidor durante 10 segundos
Luego hay muchas solicitudes de clientes, cada una de las cuales ocupa tanto tiempo
Entonces las capacidades de unificación del servidor se reducirán considerablemente
Esto no se debe a que el servidor tenga muchas tareas pesadas, sino solo porque el hilo de servicio esté esperando IO (porque aceptar, leer y escribir están bloqueando)
Es muy no remunerado dejar que las CPU de alta velocidad esperen su ineficiente red IO
¿Qué debo hacer en este momento?
NiO
El nuevo IO resolvió con éxito el problema anterior. ¿Cómo lo resolvió?
La unidad mínima para IO para procesar las solicitudes del cliente es el subproceso
NIO usa una unidad que es un nivel más pequeña que un hilo: canal (canal)
Se puede decir que solo se necesita un hilo en NIO para completar toda la recepción, lectura, escritura y otras operaciones.
Para aprender a NIO, primero debes entender sus tres puntos centrales
Selector, selector
Búfer, amortiguador
Canal, canal
El blogger no tiene talento, por lo que dibujó una imagen fea para profundizar su impresión^. ^
Dame otro diagrama de flujo de trabajo de NIO bajo TCP (muy difícil de dibujar líneas ...)
Solo entiéndalo aproximadamente, vamos paso a paso
Buffer
En primer lugar, necesitas saber qué es un búfer
La interacción de datos ya no usa transmisiones como mecanismos IO
En su lugar, use un búfer (búfer)
El blogger piensa que las imágenes son las más fáciles de entender
entonces...
Puedes ver dónde está el búfer en todo el flujo de trabajo
Echemos un vistazo a los reales. El código específico en la figura anterior es el siguiente:
1. Primero asigne espacio al búfer, en bytes
Bytebuffer bytebuffer = bytebuffer.allocate (1024);
Crear un objeto ByteBuffer y especificar el tamaño de la memoria
2. Escriba datos al búfer:
1). Datos del canal al búfer: canal.read (bytebuffer); 2). Datos del cliente al búfer: bytebuffer.put (...);
3. Lea los datos del búfer:
1). Datos de búfer a canal: canal.write (bytebuffer); 2). Datos del búfer al servidor: bytebuffer.get (...);
Selector
El selector es el núcleo de NIO, es el administrador del canal
Al ejecutar el método de bloqueo select (), escuche si el canal está listo
Una vez que los datos son legibles, el valor de retorno de este método es el número de keys de selección
Por lo tanto, el servidor generalmente ejecuta el bucle select () del método muerto hasta que el canal esté listo y luego comienza a funcionar
Cada canal unirá un evento al selector y luego generará un objeto SelectionKey.
Lo que debe tenerse en cuenta es:
Cuando el canal y el selector están vinculados, el canal debe estar en modo no bloqueado.
Filechannel no puede cambiar al modo sin bloqueo porque no es un canal de socket, por lo que Filechannel no puede vincular eventos con Selector
Hay cuatro tipos de eventos en Nio:
1.selectionKey.op_connect: evento de conexión
2.SelectionKey.op_accept: Recibir eventos
3.selectionKey.op_read: leer eventos
4.selectionKey.op_write: escribir eventos
Canal
Hay cuatro canales en total:
FileChannel: actúa en la transmisión de archivos IO
DataGramChannel: funciona en el protocolo UDP
Socketchannel: funciona en el protocolo TCP
ServerSocketchannel: actúa sobre el protocolo TCP
Este artículo explica NIO a través del protocolo TCP comúnmente utilizado
Tomemos Serversocketchannel como ejemplo:
Abra un canal Serversocketchannel
SERVERSOCHECHECHECHANNEL SERVERSOCHECHEnCHannel = SERVERSOCHECHECHANNEL.OPEN ();
Cierre el canal Serversocketchannel:
ServerSocketchannel.close ();
Bucle -socketchannel:
while (true) {Socketchannel Socketchannel = ServerSocketchannel.accept (); ClientChannel.ConfigureBlocking (falso);} clientChannel.configureBlocking(false); La declaración es establecer este canal en no bloqueo, es decir, el control libre asincrónico del bloqueo o el no bloqueo es una de las características de NIO
Selección
SelectionKey es el componente central de la interacción entre canales y selectores
Por ejemplo, vincule un selector en el Socketchannel y regístreselo como un evento de conexión:
Socketchannel ClientChannel = Socketchannel.open (); ClientChannel.ConfigureBlocking (false); ClientChannel.Connect (nuevo inetSocketAddress (puerto)); ClientChannel.register (selector, selectionKey.op_connect);
El núcleo está en el método registro (), que devuelve un objeto SelectionKey
Para detectar eventos de canal es qué tipo de evento puede usar el siguiente método:
selectionKey.isaceptable (); selectionKey.Isconnectable (); selectionKey.IseRable (); SelectionKey.Iswritable ();
El servidor realiza las operaciones correspondientes en las encuestas a través de estos métodos.
Por supuesto, las teclas unidas al canal y al selector también se pueden obtener a su vez.
Canal canal = selectionKey.channel (); selector selector = selectionKey.selector ();
Al registrar eventos en el canal, también podemos unir un búfer:
ClientChannel.Register (key.selector (), selectionKey.op_read, bytebuffer.allocatedIrect (1024));
O vincular un objeto:
selectionKey.attach (objeto); objeto anthorobj = selectionKey.attachment ();
Servidor TCP de Nio
Después de hablar tanto, echaremos un vistazo al código más simple y más central (no es elegante agregar tantos comentarios, pero es conveniente que todos lo entiendan):
paquete cn.blog.test.niotest; import java.io.ioException; import java.net.inetsocketaddress; import java.nio.bytebuffer; import java.nio.channels.*; import java.nio.charset.charset; import java.util.iterator; import java.util.set; público mynioserver {private Selector Selector Selector Selector Selectors; // crear un selector privado final int Port = 8686; Private final estática int buf_size = 10240; Private void initServer () lanza IOException {// Crea Channel Manager Object Selector this.selector = selector.open (); // Cree un objeto de canal Channel ServerSocketchannel Channel = ServerSocketchannel.open (); Channel.ConfigureBlocking (falso); // Establezca el canal en canal no bloqueado.socket (). Bind (nuevo inetSocketAddress (puerto)); // Vincula el canal en el puerto 8686 // Vincula el administrador de canales y el canal anteriores, y registre el evento Op_accept para el canal // después de registrar el evento, Selector.Select () devolverá (una clave). Si el evento no alcanza Selector.Select (), bloqueará SelectionKey SelectionKey = Channel.register (Selector, SelectionKey.op_accept); while (true) {// encuestado selector.select (); // Este es un método de bloqueo, espere hasta que los datos sean legibles, y el valor de retorno es el número de claves (puede haber múltiples) establecer teclas = selector.selectedKeys (); // Si el canal tiene datos, acceda a las claves generadas en el iterador de recopilación de teclas iterator = keys.iterator (); // Obtenga el iterador de esta colección de claves mientras (iterator.hasnext ()) {// usa el iterador para atravesar la colección selectionKey tecla = (selectionKey) iterator.next (); // Obtenga una instancia clave en la colección iterator.remove (); // Recuerde eliminar este elemento en el iterador después de obtener la instancia de la clave actual, lo cual es muy importante, de lo contrario ocurrirá un error si (key.isaceptable ()) {// juzga si el canal representado por la clave actual está en el estado aceptable, y si es así, doacept (key); } else if (key.iseadable ()) {doread (key); } else if (key.iswritable () && key.isValid ()) {dowRite (key); } else if (key.isconnectable ()) {system.out.println ("¡conectable!"); }}}} public void doaccept (selectionKey key) lanza ioexception {Serversocketchannel serverChannel = (Serversocketchannel) Key.channel (); System.out.println ("Serversocketchannel está escuchando en un bucle"); Socketchannel ClientChannel = ServerChannel.accept (); ClientChannel.ConfigureBlocking (falso); ClientChannel.Register (Key.Selector (), SelectionKey.op_read); } public void doread (selectionKey key) lanza IOException {Socketchannel ClientChannel = (Socketchannel) Key.channel (); Bytebuffer bytebuffer = bytebuffer.allocate (buf_size); long bytesread = clientChannel.read (bytebuffer); while (bytesread> 0) {bytebuffer.flip (); byte [] data = byteBuffer.Array (); String info = new String (data) .trim (); System.out.println ("El mensaje enviado desde el cliente es:"+info); bytebuffer.clear (); bytesread = clientChannel.read (bytebuffer); } if (bytesread ==-1) {clientChannel.close (); }} public void DowRite (Key Key Key) lanza ioexception {byteBuffer byteBuffer = bytebuffer.allocate (buf_size); bytebuffer.flip (); Socketchannel ClientChannel = (Socketchannel) key.channel (); while (bytebuffer.hasremaining ()) {clientChannel.write (bytebuffer); } bytebuffer.compact (); } public static void main (string [] args) lanza ioexception {mynioserver mynioserver = new mynioserver (); mynioserver.initserver (); }} Imprimí el canal del monitor y le dije cuándo comenzó a funcionar el ServidorChetchannel
Si coopera con la depuración del cliente NIO, puede encontrarla claramente. Antes de ingresar la encuesta select ()
Aunque ya hay una clave para el evento de aceptación, select () no se llamará de forma predeterminada
En su lugar, debe esperar hasta que se capturen otros eventos interesantes por select () antes de llamar a la selección de aceptación de aceptación
Solo entonces el Serversocketchetchannel comenzó a realizar monitoreo circular
En otras palabras, en un selector, el ServidorChetchannel siempre se mantiene.
Y serverChannel.accept(); es verdaderamente asíncrono (Channel.ConfigureBlocking (falso); en el método InitServer)
Si no se acepta la conexión, se devolverá un nulo
Si un Socketchannel está conectado con éxito, este Socketchannel registra un evento de escritura (Leer)
Y establecer asíncrono
Cliente TCP de Nio
Debe haber un cliente si hay un servidor
De hecho, si puede comprender completamente el servidor
El código del cliente es similar
paquete cn.blog.test.niotest; import java.io.ioException; import java.net.inetsocketaddress; import java.nio.bytebuffer; import java.nio.channels.selectionKey; import java.nio.channels.selector; import java.nio.channels.socketchannel; import javement.utator; MynioClient {private selector selector; // crear un selector privado final int Port = 8686; Private final estática int buf_size = 10240; Private static bytebuffer bytebuffer = bytebuffer.allocate (buf_size); private void initClient () lanza ioexception {this.selector = selector.open (); Socketchannel ClientChannel = Socketchannel.open (); ClientChannel.ConfigureBlocking (falso); ClientChannel.connect (nuevo inetSocketAddress (puerto)); ClientChannel.register (selector, selectionKey.op_connect); while (true) {selector.select (); Iterator <SelectionKey> iterator = selector.selectedKeys (). Iterator (); while (iterator.hasnext ()) {selectionKey key = iterator.next (); iterator.remove (); if (key.isconnectable ()) {Doconnect (Key); } else if (key.iseadable ()) {doread (key); }}} public void doconnect (Key Key Key) lanza IOException {SocketchEnchannel ClientChannel = (Socketchannel) Key.channel (); if (clientChannel.isConnectionPending ()) {ClientChannel.FinishConnect (); } clientChannel.configureBlowing (falso); String info = "¡Hello Server!"; bytebuffer.clear (); bytebuffer.put (info.getBytes ("utf-8")); bytebuffer.flip (); ClientChannel.Write (ByteBuffer); //clientChannel.register(key.selector(),selectionKey.op_read); ClientChannel.Close (); } public void doread (selectionKey key) lanza IOException {Socketchannel ClientChannel = (Socketchannel) Key.channel (); ClientChannel.read (bytebuffer); byte [] data = byteBuffer.Array (); Cadena msg = new String (data) .trim (); System.out.println ("El servidor envía mensaje:"+msg); ClientChannel.Close (); Key.Selector (). Close (); } public static void main (string [] args) lanza ioexception {mynioClient mynioClient = new mynioClient (); mynioClient.initClient (); }}Resultado de salida
Aquí abro un servidor y dos clientes:
A continuación, puede intentar abrir mil clientes al mismo tiempo. Mientras su CPU sea lo suficientemente potente, el servidor no podrá reducir el rendimiento debido al bloqueo.
Lo anterior es una explicación detallada de Java Nio. Si tiene alguna pregunta, puede discutirlo en el área de mensajes a continuación.