Article updated on

Java NIO Server Ejemplo (Bidireccional Asíncrono)

Si usas un sólo hilo por cada socket abierto en el servidor puedes consumir una candidad de recursos de memoria y CPU considerable. Con java.nio es más eficiente en este aspecto pero sube la latencia de la aplicación (no demasiado en este ejemplo)

1 - Java NIO Server

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.ArrayList;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class Server {
    private static int PORT = 8000;
    private static final long PAUSE_BETWEEEN_MSGS = 10; // millisecs
    private static ByteBuffer echoBuffer = ByteBuffer.allocate(1024);
    private static ConcurrentHashMap<Integer, SocketChannel> chm
                        = new ConcurrentHashMap<Integer, SocketChannel>();
    private static int msg = 0;
    public static void main(String args[]) throws Exception {
        // Create a new selector
        Selector selector = Selector.open();
        // Open a listener on each port, and register each one
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ServerSocket ss = ssc.socket();            
        InetSocketAddress address = new InetSocketAddress(PORT);
        ss.bind(address);
        //registers ACCEPT
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("Going to listen on " + PORT);
        sendMsgsToRandomClients();        
        while (true) {
            selector.select();
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> it = selectedKeys.iterator();            
            String msg = new String();            
            while (it.hasNext()) {
                SelectionKey key = (SelectionKey) it.next();
                if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
                    // Accept the new connection
                    ServerSocketChannel sscNew = (ServerSocketChannel) key
                            .channel();
                    SocketChannel sc = sscNew.accept();
                    sc.configureBlocking(false);
                    // Add the new connection to the selector                    
                    sc.register(selector, SelectionKey.OP_READ);
                    // Add the socket channel to the list
                    chm.put(sc.hashCode(), sc);
                    it.remove();
                } else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
                    // Read the data
                    SocketChannel sc = (SocketChannel) key.channel();            
                    int code = 0;
                    while ((code = sc.read(echoBuffer)) > 0) {
                        byte b[] = new byte[echoBuffer.position()];
                        echoBuffer.flip();
                        echoBuffer.get(b);
                        msg+=new String(b, "UTF-8");
                    }
                    // removes the new line
                    if(msg.length()>1)
                           msg = msg.substring(0, msg.length()-2);
                    
                    if (code == -1 ||
                        msg.toUpperCase().indexOf("BYE")>-1){
                        chm.remove(sc.hashCode());
                        sc.close();
                    } else {
                        echoBuffer.clear();
                    }
                   System.out.println("msg: " + msg  + " from: " + sc + "code:  " + code );
                   it.remove();
                }
            }
        }        
    }        
    /**
     * This method sends messages to a random outPutStream
     */
    private static void sendMsgsToRandomClients() {
        new Thread("Send-to-Clients") {
            public void run() {
                try {
                    while (true) {
                        Random generator = new Random();
                        if(chm.keySet().size()>0){
                            Integer randomKey = new ArrayList<Integer>(
                                    chm.keySet()).get(generator.nextInt(chm.keySet().size()));
                            SocketChannel sc = chm.get(randomKey);
                            try {
                                msg++;                                
                                ByteBuffer buf = ByteBuffer.wrap(("From server to Client msg nº - "+ msg + "\n").getBytes());
                                sc.write(buf);
                            } catch (IOException e) {
                                e.printStackTrace();
                                chm.remove(randomKey);
                            }                            
                        }
                        Thread.sleep(PAUSE_BETWEEEN_MSGS);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}

 

1.1 Arrancar el Servidor

Compila y Arranca el servidor. Si es desde consola javac Server.java para compilar y  Java Server para ejecutar. Si todo va bien deberías ver.

1.2 Prueba el Servidor

Usa telnet o similar para verificar que el cliente funciona. Telnet the Server ex. telnet localhost 8000 . (Si usas windows mas reciente que el XP tendrás que instalarlo o usar un cliente socket cualquiera)

Escribe "bye" y pulsa Entrar para cerrar la sesión con el servidor.  Puedes usar tantos clientes telnet como quieras desde diferentes ordenadores o el mismo. Cuando se terminan todas las conexiones con el servidor, este muestra los resultados.

2 - Notas

  • Si en Linux se produce el error Too Many Open files lanza el comando ulimit -n 4096 para 4096 archivos.
  • Si tienes alguna idea o sugerencia aquí