3/28/2010

Desarrollo rápido de aplicaciones de red: Apache Mina (II) - Nociones básicas

En el artículo anterior explicaba como preparar el entorno de trabajo necesario para desarrollar aplicaciones de red utilizando los framework J2EE, Eclipse y Apache MINA. Un buen punto de partida es como siempre la "Quick start guide" del proyecto, en ella se explican los pasos básicos para la construcción de una aplicación con MINA, en este caso a través del ejemplo del servidor de hora probado con anterioridad, no tiene sentido explicar ese mismo ejemplo así que me centraré en el servidor reverser que es muy similar. No entraré en los parámetros de los métodos de las clases, para eso tenemos la API, ni en el funcionamiento interno del framework, en la guía de usuario que aunque no está completa todavía, sí aborda algo el tema en el apartado 2. Como comenté en la entrada anterior solo daré algunas nociones que puedan ayudar a alguien intentando que esto sea un complemento a la documentación oficial del proyecto ya que solo se explica el código de algún ejemplo y no existe demasiado en castellano sobre MINA.


El paquete del reverser(org.apache.mina.example.reverser) tiene 2 clases y una archivo .html con la descripción del mismo. La clase Main.java es la principal del proyecto como su nombre indica y ReverserProtocolHandler.java tampoco es muy difícil de suponer que va a ser el manejador de los eventos que producen las conexiones entrantes.
Comenzamos la inspección del código por la clase Main.java:



Esta clase es prácticamente igual que en el caso del servidor de hora del ejemplo de la documentación, solo cambia la línea 4 que ahora comentaremos:

1 - La clase NioSocketAcceptor.java es el objeto que se encarga de escuchar las conexiones entrantes.
2 y 3 - Los filtros funcionan como muestra la siguiente imagen enlazada desde éste artículo de la documentación oficial donde se explica todo esto en profundidad. La idea se resume en que el mensaje lo recibe la aplicación por el acceptor y va "subiendo" desde el nivel más bajo de la cadena de responsabilidad a medida que se le van aplicando filtros de la cadena(ioFilterChain) definida sobre ese aceptor cuya misión es tratar las tramas de bits recibidos. Como podemos deducir de lo anterior el orden en que se le aplican es importante ya que, como acabo de decir, la trama recibida se trata secuencialmente.


En nuestro ejemplo se aplican dos filtros(se pueden aplicar todos los que se necesiten en función de nuestras necesidades):
- logger: Crea logs sobre las conexiones, aquí tenemos más información sobre el sistema de logs de MINA.
- codec: Define como se codifican/descodifican las tramas enviadas/recibidas, por ejemplo en el caso de las tramas recibidas dice como debe de interpretar los bytes la aplicación para poder utilizarlos. En este caso utiliza un codec que incorpora MINA para las tramas de texto que son las que necesita este servidor reverser, al igual que pasaba con el servidor de tiempo. Este sistema modular nos permite que la aplicación soporte nuevos protocolos siendo necesario solamente definir un nuevo codec.
4 - Establece en manejador para los eventos producidos por nuestro aceptor(conexiones entrantes), es el que contendrá la lógica de negocio de la aplicación y por lo tanto es donde centraremos nuestro desarrollo.
5 - Hace el bind del socket con el puerto.

Nos queda definir la lógica de negocio en el manejador de nuestra aplicación, en el ejemplo ésta reside en la clase ReverseProtocolHandler.java.




De aquí nos interesa el método messageReceived(), el propio framework nos ofrece en el parámetro message la trama de bits recibida y en el parámetro session la sesión TCP para poder responderle, el desarrollador no tiene que preocuparse de nada más. La lógica de negocio en este caso es convertir la trama a tipo String y darle la vuelta a los caracteres. A continuación se escribe en la sesión, en este caso en forma de tipo String por tener definido el codec para texto como se explicó mientras hablábamos de los filtros. Si no se hubiera definido niguno habría que enviar los bytes usando para ello algún método del objeto a enviar para obtenerlos(.getBytes() normalmente). Para hacer una pequeña prueba podemos crear una cadena nueva y enviarla añadiendo esta línea al final y vemos que ahora el reverser es más simpático... :) :
String probing = ":)"; session.write(probing);

(Nota: El telnet de Windows hace cosas un poco raras, desconozco el porqué pero para nuestras pruebas es suficiente, en el artículo anterior las pruebas eran con un Debian y funciona perfectamente.)




El método messageReceived() lo invoca el framework cuando se produce un evento(llegada de un mensaje) pero podemos añadir comportamiento a la aplicación definiendo otros, ¿cuales defino? Si miramos el código de ReverseProtocolHandler.java vemos que extiende a la clase IoHandlerAdapter.java redefiniendo sus métodos, así que nos vamos a la API y encontramos que además tiene los siguientes métodos: exceptionCaught(), messageSent(), sessionClosed(), sessionCreated(), sessionIdle() y sessionOpened(). Los nombres son lo suficientemente explicativos así que para terminar vamos a hacer otra pequeña prueba para comprender un poco mejor el funcionamiento y de paso hacer nuestro servidor un poco más educado y conseguimos que nos salude al conectarnos. Redefinimos el método sessionOpened() añadiendo el siguiente trozo de código antes del método messageReceived() y listo :) .


@Override
public void sessionOpened(final IoSession session){
String probing = "Hi!, how are you?";
session.write(probing);
}



Intentaré escribir alguna entrada más tratando más en profundidad temas como la gestión de logs, conexiones asíncornas o la creación de codecs nuevos.


Jesús Pérez