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. En el enfoque sin bloqueo, un subproceso puede manejar múltiples 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. En este artículo, vemos cómo implementamos nuestro propio servidor y cliente sin bloqueo. Analicemos primero los componentes y las clases de Java NIO que implementan los canales de Java NIO.
Selectores
El Selector es una de las clases de Java NIO. Los canales que queremos escuchar deben estar registrados en Selector. A cada canal se le asigna una tecla de selección. SelectionKey es un objeto que identifica un canal y contiene información sobre el canal, como si el canal está listo para aceptar la conexión. Contiene información sobre el tipo de solicitud y quién realiza la solicitud. La instancia de Selector puede monitorear más canales de socket y también informa a la aplicación para que procese la solicitud. Podemos crear un selector llamando al método Selector.open() :
Selector selector = Selector.open();
tampones
Los búferes definen la funcionalidad central que es común a todos los búferes: el límite, la capacidad y la posición actual. Los búferes Java NIO se utilizan para interactuar con los canales NIO. Es el bloque de memoria en el que podemos escribir datos, que luego podemos volver a leer. El bloque de memoria está envuelto con un objeto de búfer NIO, que proporciona métodos más fáciles para trabajar con el bloque de memoria. Hay un búfer para cada tipo de datos básicos:
- CharBuffer
- búfer doble
- IntBuffer
- Buffer de bytes
- Búfer corto
- Buffer flotante
- búfer largo
Sintaxis para asignar el ByteBuffer con la capacidad de 1024 bytes :
ByteBuffer buffer = ByteBuffer.allocate(1024);
Clases que implementan el canal Java NIO
SocketChannel
Java NIO SocketChannel se utiliza para conectar un canal con un socket de red TCP. Es equivalente a Java Networking Sockets utilizado en la programación de redes. También utiliza los métodos de fábrica para crear el nuevo objeto. Se puede crear un SocketChannel en Java NIO:
- Cuando una conexión entrante llega al ServerSocketChannel.
- Para conectarse con un servidor en cualquier lugar a través de Internet.
Podemos abrir un SocketChannel llamando al método SocketChannel.Open() .
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 8089));
ServerSocketChannel
El ServerSocketChannel de Java NIO también se utiliza para conectar un canal con un socket de red TCP. Es equivalente a Java Networking Sockets utilizado en la programación de redes. La clase de ServerSocketChannel se encuentra en el paquete java.nio.channels . Un canal de socket de servidor se crea invocando el método .open() de esta clase. Un canal de socket de servidor recién creado está abierto pero aún no está vinculado. Un intento de invocar el método .accept() de un canal de socket de servidor no enlazado hará que se lance una excepción NotYetBoundException . Un canal de socket de servidor se puede vincular invocando uno de los métodos de vinculación definidos por esta clase.
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress("localhost", 8089)); while(true) { SocketChannel socketChannel = serverSocketChannel.accept(); }
Pasos para crear un servidor sin bloqueo
Estructura del proyecto (IntelliJ IDEA)
Paso 1: Abra el selector que maneja el canal
Creamos un objeto Selector llamando al método estático .open() de la clase Selector. Se utiliza un Selector para mantener una referencia a un conjunto de canales y se le puede solicitar que proporcione un conjunto de canales que estén listos para aceptar la conexión.
Sintaxis:
selector = Selector.open();
Paso 2: vincular el puerto del servidor
Creamos ServerSocketChannel llamando al método .open() , luego llamamos al método .configureBlocking() en él, pasando falso como parámetro para decirle al canal que no bloquee. Luego obtenemos el ServerSocket del canal llamando al método .socket() . Vinculamos el socket al puerto 8089 , para que el servidor pueda comenzar a escuchar en ese puerto.
ServerSocketChannel socket = ServerSocketChannel.open(); socket.configureBlocking(false); ServerSocket serverSocket = socket.socket(); serverSocket.bind(new InetSocketAddress("localhost", 8089));
Paso 3: Registre ServerSocket en Selector
Necesitamos registrar nuestro servidor con el selector para que cuando se requiera realizar operaciones, se seleccione el canal del servidor y se realicen las operaciones. Esto se puede hacer llamando al método .register() . Esto registra el objeto de ServerSocketChannel para aceptar conexiones entrantes.
socket.register(selector, ops, null);
Paso 4: espera el evento
Ahora ingresamos un ciclo while infinito y luego llamamos al método .select() del selector, que selecciona un conjunto de teclas cuyos canales correspondientes están listos para operaciones de E/S. Este método realiza una operación de selección de bloqueo. Vuelve solo después de seleccionar al menos un canal.
selector.select();
Paso 5: obtenga las claves de selección
Podemos obtener las claves seleccionadas que requieren que se realice una operación llamando al método .selectedKeys() del selector, que devuelve las claves como un conjunto.
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Usamos un iterador llamando al método .iterator() que ayuda a recorrer todas las claves del conjunto. Obtenemos la clave llamando al método .next() del iterador.
Iterator<SelectionKey> i = selectedKeys.iterator();
Paso 6: Compruebe si el cliente está listo para aceptar
Para probar si un canal correspondiente a una clave está listo para aceptar una nueva conexión de socket, usamos el método .isAcceptable() . Se comporta exactamente de la misma manera que la expresión key.readyOps() & OP_ACCEPT != 0
if (key.isAcceptable()) { // New client has been accepted }
Paso 7: Lee el mensaje del Cliente
Registramos el canal para una operación de lectura, ya que una vez que se acepta la conexión del cliente, el cliente enviará el mensaje de vuelta al servidor.
client.register(selector, SelectionKey.OP_READ);
Leerá los datos del canal y los colocará en el búfer. Después de eso, enviaremos datos desde el búfer a la pantalla.
Paso 8: Cierra la conexión
Ahora cierra la conexión con el cliente utilizando el método .close() del objeto SocketChannel .
A continuación se muestra la implementación de los pasos antes mencionados:
Archivo: Servidor.java
Java
/*package whatever //do not write package name here */ import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class Server { private static Selector selector = null; public static void main(String[] args) { try { selector = Selector.open(); // We have to set connection host,port and // non-blocking mode ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); ServerSocket serverSocket = serverSocketChannel.socket(); serverSocket.bind( new InetSocketAddress("localhost", 8089)); serverSocketChannel.configureBlocking(false); int ops = serverSocketChannel.validOps(); serverSocketChannel.register(selector, ops, null); while (true) { selector.select(); Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> i = selectedKeys.iterator(); while (i.hasNext()) { SelectionKey key = i.next(); if (key.isAcceptable()) { // New client has been accepted handleAccept(serverSocketChannel, key); } else if (key.isReadable()) { // We can run non-blocking operation // READ on our client handleRead(key); } i.remove(); } } } catch (IOException e) { e.printStackTrace(); } } private static void handleAccept(ServerSocketChannel mySocket, SelectionKey key) throws IOException { System.out.println("Connection Accepted.."); // Accept the connection and set non-blocking mode SocketChannel client = mySocket.accept(); client.configureBlocking(false); // Register that client is reading this channel client.register(selector, SelectionKey.OP_READ); } private static void handleRead(SelectionKey key) throws IOException { System.out.println("Reading client's message."); // create a ServerSocketChannel to read the request SocketChannel client = (SocketChannel)key.channel(); // Create buffer to read data ByteBuffer buffer = ByteBuffer.allocate(1024); client.read(buffer); // Parse data from buffer to String String data = new String(buffer.array()).trim(); if (data.length() > 0) { System.out.println("Received message: " + data); if (data.equalsIgnoreCase( "I Love KirikoChan")) { client.close(); System.out.println("Connection closed..."); } } } }
Archivo: Cliente.java
Java
/*package whatever //do not write package name here */ import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public class Client { public static void main(String[] args) { try { String[] messages = { "Non-Blocking servers are the best.", "I Love GeeksForGeeks", "I Love KirikoChan" }; System.out.println( "Connection accepted by the Server.."); SocketChannel client = SocketChannel.open( new InetSocketAddress("localhost", 8089)); for (String msg : messages) { ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.put(msg.getBytes()); buffer.flip(); int bytesWritten = client.write(buffer); System.out.println(String.format( "Sending Message: %s\nbufforBytes: %d", msg, bytesWritten)); } client.close(); System.out.println("Client connection closed"); } catch (IOException e) { e.printStackTrace(); } } }
Conectando el cliente a nuestro servidor
Ahora tenemos un cliente y un servidor Non-Blocking listos para la comunicación. Primero, ejecute .main() dentro del archivo Server.java , debe estar listo cuando el cliente envíe un mensaje.