Chat escalable en Java usando NetBeans 6.5

domingo, 30 de noviembre de 2008
Introducción
Hace tiempo creé una pequeña aplicación para chat, un cliente y un servidor.

Si bien ese chat funciona bien, no es escalable. Cada vez que se conecta un cliente, el servidor crea un hilo para gestionarlo. Si se conectan mil personas, se crean mil hilos. Como ya se darán cuenta, mediante esa forma de trabajar el consumo de CPU y memoria RAM se elevan mucho a medida que se conectan mas y mas personas.

Los hilos sirven, permiten hacer varias cosas a la vez sin que se bloquee al usuario, pero la verdad, para una aplicación chat no es recomendable.

Mirando la API de java NIO (New Input Output) mas algunos foros, blogs y tutoriales se puede ver que ésta permite hacer aplicaciones escalables con flujos de datos (Sockets, archivos, etc.). Esta API tiene una clase llamada Selector, la cual se encarga de manejar Sockets no bloqueantes que no son mas que canales para transmisión de flujos de datos.

NIO es una solución para aplicaciones escalables en java, no es necesario crear hilos para cada cliente y literalmente puede manejar miles de clientes con un bajo consumo de recursos.

El problema con NIO es que es de muy bajo nivel, lo único que puede viajar por la red son objetos de tipo ByteBuffer, pero que pasa cuando los datos que se quieren transmitir son de diferentes largos ? que pasa si me paso en el tamaño establecido por el buffer ? o si me sobra ? al ser de bajo nivel hay que gestionar demasiados casos, demasiados parámetros, como mencionaba es de bajo nivel. Y justamente este problema lo tuve al desarrollar un nuevo chat usando NIO. Cuando enviaba muchos emoticones el mensaje se perdía por el tema del buffer.

Los ejemplos en Internet abundan, pero los ejemplos decentes .... no encontré ni uno solo, solo se encuentran cosas como para enviar un par de palabras, nada que permita a un usuario hacer algo mas grande o mas normal como enviar texto mezclado con imágenes o url's.

Buscando algo que encapsulara y subiera de nivel a NIO llegué a un proyecto open source muy muy muy bueno, se llama XSocket. Esta es una API que encapsula a NIO encargándose de todo lo que es de bajo nivel permitiendo al desarrollador preocuparse por la implementación de lo que es importante (la lógica de la comunidad java) y no de los detalles (importantes) de bajo nivel.

Migré mi código inicial de NIO a XSocket y todo funcionó a la perfección.

Características
La aplicación que desarrollé soporta:
  • Emoticones
  • Color del texto cambiable
  • Estados
  • Mensaje personal
  • Avatares
  • URL's
  • Chat global o general
  • Chat privados
  • Pestañas
  • Multi usuario
Lo que no soporta este chat es envío de archivos, tal vez alguno de ustedes lo puede hacer y compartir con los demás, así hacemos un chat mas poderoso.

Mensajes
Los mensajes que se transmiten son texto con estructura XML, un mensaje podría tener la siguiente estructura:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<raiz>

<tipo></tipo>

<remitente>metalklesk</remitente>

<avatar></avatar>

<color></color>

<mensaje>

<texto>Este es un ejemplo de texto junto a un emoticon</texto>

<emoticon></emoticon>

<texto>y al lado hay un link</texto>

<url>http://metalklesk.blogspot.com</url>

<texto>que se puede pinchar para abrirlo en un navegador web.</texto>

</mensaje>

<destinatario>

<usuario></usuario>

<usuario></usuario>

<usuario></usuario>

<usuario></usuario>

</destinatario>

</raiz>

Protocolo de mensajería
El protocolo de mensajería se basa en etiquetas o tags que definen ciertas características.
El tag tipo establece el tipo de mensaje que se transmite. Estos tipos tienen un valor entero y cada valor tiene su significado particular. A continuación se pueden ver los tipos y su significado:
-2: conexión aceptada desde el servidor
-1: conexión rechazada desde el servidor, nickname ya está en uso
1 : aviso/petición de conexión
2 : mensaje broadcast (para todos)
3 : cliente ha cambiado de estado
4 : cliente ha cambiado de avatar
5 : cliente ha cambiado su mensaje personal
6 : mensaje unicast (solo para uno)
Mediante el establecimiento de tipos de mensajes, la gestión de los mismos se torna muy fácil, simplemente se lee el tipo de mensaje y se gestiona según corresponda.

El uso de una estructura XML permite crear un mensaje ordenado, donde cada parte esta bien definida y además se pueden agregar más sin cambiar o modificar lo ya hecho.

Mediante este esquema se pueden enviar mensajes con texto, emoticones y URL's, en cualquier orden y todo lo que se quiera. Se pueden enviar mensajes broadcast o unicast (multicast no está implementado pero el protocolo lo permite), se pueden usar nicknames, estados, mensajes personales, etc.

Funcionamiento interno
Formateando el mensaje
Para el funcionamiento interno se debe construir un mensaje con estructura XML a partir del texto que el usuario escriba en el chat. Para esto escribí una clase que se llama FiltroMensajeFormatoXML, el cual hace un parseo de la linea de texto y separa en texto, emoticones y URL's retornando un ArrayList con el contenido del mensaje pre-formateado. Los emoticones son imágenes, éstas imágenes son transformadas a texto codificándolas en Base64.

El texto a formatear puede ser el siguiente:
Hola mundo! [emo]ohaha[/emo], visita mi blog [url]http://metalklesk.blogspot.com[/url]
Y la salida pre formateada sería la siguiente
[texto]Hola mundo!
[emoticon](emoticon en texto)
[texto], visita mi blog
[url]http://metalklesk.blogspot.com

Una ves pre-formateado se crea un objeto de la clase MensajeFormatoXML, al cual se le pasa el contenido del mensaje, el tipo de mensaje y todos los datos que correspondan según el tipo de mensaje. Ésta clase tiene un método que crea un documento XML con toda la información ingresada al objeto y retorna un String con el mensaje construido y formateado.
El mensaje quedaria de ésta forma:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<raiz>

<tipo>2</tipo>

<remitente>metalklesk</remitente>

<avatar>imagen codificada en base 64</avatar>

<color>representacion del color como entero</color>

<mensaje>

<texto>Hola mundo! </texto>

<emoticon>imagen codificada en base 64</emoticon>

<texto>, visita mi blog</texto>

<url>http://metalklesk.blogspot.com</url>

</mensaje>

<destinatario>

</destinatario>

</raiz>


Una vez listo el mensaje está listo para ser enviado.

Conexión al servidor
Para conectarse al servidor del chat se crea una instancia de la clase NonBlockingConnection del API XSocket. Esta clase recibe el ip o hostname del servidor, el puerto y un manejador de la entrada de datos (una instancia de la clase IDataHandler).

Envío y recepción del mensaje
Para enviar un mensaje simplemente se utiliza la linea:
nbc.write(mensaje.getMensajeXML() + "\n", "UTF-8");

y el mensaje en formato de texto XML es enviado al servidor.

Cuando el servidor envía una respuesta, ésta es capturada en el objeto IDataHandler mediante el método onData, y se utiliza la siguiente línea para obtener el texto:
String res = nbc.readStringByDelimiter("\n", "UTF-8");

Luego se crea una instancia de la clase ParserMensajeXML que se encarga de crear un documento XML en memoria con el texto y luego se obtiene un objeto MensajeFormatoXML con el mensaje listo para ser leído utilizando los métodos GET definidos.

public boolean onData(INonBlockingConnection nbc) throws IOException, BufferUnderflowException,MaxReadSizeExceededException {

//aqui recibo el mensaje entrante

String res = nbc.readStringByDelimiter("\n", "UTF-8");

//aqui parseo el mensaje

ParserMensajeXML xml = new ParserMensajeXML(res);

//aqui obtengo el mensaje

MensajeFormatoXML mensaje = xml.getMensaje();

//aqui lo envio a todos los que están a la escucha del mensaje entrante.

RepetidorMensajeEntrante.getInstance().enviarMensaje(mensaje);

return true;

}
Lo demás del chat cliente es solo el manejo de la presentación del mensaje, eventos y algunas cosas entretenidas que no tienen nada que ver con la transmisión de datos por la red.

Utilización
Para utilizar el chat primero se debe ejecutar el servidor. Para esto simplemente se debe ejecutar en consola en cualquier Sistema Operativo que soporte Java:
java -jar ServidorNIO.jar

o ejecutar en Linux
./run.sh

o en Windows (se debe renombrar el archivo run.bat.txt a run.bat)
./run.bat
No olvidar que se debe tener instalada la máquina virtual de java, recomiendo la última versión a la fecha.

Capturas de pantalla
Aquí dejo algunas capturas de pantalla mostrando la ejecución de la aplicación




Descarga !!!
Todo el código está muy bien comentado, así es que lo pueden leer, entender, aprender y modificar.
Esta aplicación, cliente y servidor, está bajo la licencia GPL V3.
Para ver el código y modificarlo deben usar NetBeans 6.5.
Aquí está el cliente y aquí está el servidor.

UPDATE 19-12-2008
He subido la versión 2.1 con los siguientes cambios:

  • Se corrige un error que unos amigos lectores avisaron mediante sus comentarios. El error se refiere a la creación (escritura) y lectura del archivo de configuración config.xml en la clase Configuracion.java. El error ocurre porque cuando se crea el archivo XML, la máquina virtual de java lo crea utilizando la codificación de caracteres por default del sistema operativo en el cual se ejecuta y al leer el archivo XML le especifiqué que el archivo estaba en UTF-8, lo cual no es cierto en el caso de Windows (en Linux ningún problema). La solución fue simplemente forzar la escritura del archivo en UTF-8.
  • También agregué soporte para los temas (UI, User Interface) del proyecto substance.
  • Actualicé XSocket a la versión 2.3.
  • Agregué el soporte para enviar zumbidos en los mensajes privados (la ventana tiembla !!!).
  • Eliminé código innecesario en el servidor y en el cliente (ahora son menos lineas de código).

Aquí está el cliente y aquí está el servidor.

Para descargar de los links anteriores se debe dar un click donde dice Request Download Ticket y luego Download (o Descargar).

UPDATE 26-04-2009
Hasta que consiga un repositorio que no me de problemas para compartir archivos, por favor dejarme sus correos en un comentario y les reboto el cliente y el servidor por esa vía. Blogger me avisa cuando me hacen comentarios y mi correo lo estoy viendo todo el día, por lo que no me demoraré mucho en responderles (probablemente dentro del mismo día les conteste).

UPDATE 18-08-2009
He creado un proyecto en Google Code así es que ahora pueden descargar el chat desde ahí sin tener que pedírmelo por correo o por el blog. Aún así agradeceré los comentarios que me puedan dejar en el blog.
El enlace es el siguiente: http://code.google.com/p/jtricahuenio/.

He realizado unos pequeños cambios desde la versión 2.1,la versión para descargar desde el enlace anterior es la 2.1.1:

  • Actualicé xSocket a la versión 2.5.4.
  • Agregué el proyecto Base64 para reemplazar la implementación de Sun para la codificación y decodificación de las imágenes (avatares y emoticones) en el chat, ahora funciona impecable con OpenJDK.
  • Actualicé substance a la versión 5.2 agregando 3 nuevos themes (Dust, Dust Coffe y Twilight).
  • El código fuente viene en un proyecto para NetBeans 6.7.1.

Saludos !!!

Instalación de Openbravo 2.40 en Windows XP (SP3)

miércoles, 26 de noviembre de 2008
Me han pedido una guía de instalación sobre Windows XP de Openbravo 2.40. Y aunque no uso ese sistema operativo, lo instalé en una máquina virtual solo para hacer esta entrada.

Lo primero es lo primero, algunos se preguntarán qué es Openbravo, Openbravo es un ERP (Enterprise Resource Planning) opensource diseñado para las MIPyME (micro, pequeña y mediana empresa).
Con esta herramienta de gestión los empresarios pueden controlar los diferentes aspectos de su negocio a través de los módulos que provee.

Esta herramienta de gestión empresarial está disponible para la descarga gratuita desde el sitio web oficial de Openbravo y NO se requiere el pago de licencias para su uso en ambiente personal ni comercial.

Openbravo no es un software que se descargue e instale en un par de clicks ... bueno en realidad si pero requiere de algunas dependencias previas y una pequeña configuración del sistema para su instalación y ejecución.

Requerimientos:
Bien, para la realización de esta guía se utilizaron la última versión de Openbravo (2.40), Ant (1.7.1), Tomcat (6.0.18) y JDK (1.6.0_10). La versión de PostgreSQL que utilizé es la 8.3.5. Hay que tener en cuenta que Openbravo funciona con PostgreSQL desde la versión 8.1.4 en adelante.

Instalación de dependencias:

PostgreSQL.
Para descargar PostgreSQL vamos acá. Ahí verán dos opciones, la One click installer y pgInstaller.


La primera parece ser la mejor opción, pero no lo es. Al final de la instalación sale un error que no permite iniciar la base de datos así es que recomiendo descargar la segunda opción (pgInstaller).
Luego seleccionamos la version 8.3.5 para win32 y seleccionamos el archivo que se llama postgresql-8.3.5-1.zip y lo descargamos. Una vez descargado se descomprime, entramos a la carpeta y ejecutamos el instalador (postgresql-8.3.exe).

Aparecerá el wizard de instalación, seleccionamos el idioma (English)


seleccionamos las opciones de instalación por default


luego ingresamos la contraseña de la cuenta de servicio, luego seleccionamos la localización y DEBEMOS seleccionar UTF8 para el encoding del servidor y el cliente, luego ingresamos la contraseña de administrador del usuario postgres (debe ser distinta a la contraseña de la cuenta servicio)


y luego hacemos click siguiente siguiente siguiente para terminar con la configuración y la instalación comenzará. Al final desmarcamos la opción de instalar el Stack Builder y finalizamos.

Tomcat.
Ahora vamos a descargar la última versión de apache tomcat. Acá descargamos la versión 6.0.18. Una vez descargado lo descomprimimos y movemos la carpeta a C:\.

Ant.
Ahora vamos a descargar la última versión de apache ant. Acá descargamos la versión 1.7.1. Una vez descargado lo descomprimimos y movemos a C:\.


JDK.
Ahora vamos a descargar la última versión del JDK. Acá descargamos la versión Java SE Development Kit (JDK) 6 Update 10. Una vez descargado ejecutamos e instalamos.

Configuración del sistema:
Una vez todo instalado debemos configurar las variables de entorno. Si no las configuramos no podremos instalar Openbravo.
Mi PC -> Propiedades -> Opciones Avanzadas -> Variables de Entorno

en Variables de usuario o sistema, click en Nueva:
Nombre de la variable: ANT_HOME Valor de variable: la dirección completa de donde se encuentra la carpeta del Ant (C:\apache-ant-1.7.1)


en Variables de usuario o sistema, click en Nueva:
Nombre de la variable: CATALINA_HOME
Valor de variable: la dirección completa de donde se encuentra la carpeta del tomcat (C:\apache-tomcat-6.0.18)

en Variables de usuario o sistema, click en Nueva:
Nombre de la variable: JAVA_HOME
Valor de variable: la dirección completa de donde se encuentra la carpeta del JDK (C:\Archivos de programa\Java\jdk1.6.0_10)

en Variables de usuario o sistema, click en Nueva:
Nombre de la variable: JRE_HOME
Valor de variable: la dirección completa de donde se encuentra la carpeta del JRE (C:\Archivos de programa\Java\jdk1.6.0_10\jre)

en Variables de usuario o sistema, click en Nueva:
Nombre de la variable: JDK_HOME
Valor de variable: la dirección completa de donde se encuentra la carpeta del JDK (C:\Archivos de programa\Java\jdk1.6.0_10)

Luego debemos modificar la variable PATH e incluir al final del valor lo siguiente:
;%ANT_HOME%\bin;%CATALINA_HOME%\bin;%JAVA_HOME%\bin;%JRE_HOME%\bin;%JDK_HOME%\bin


Guardamos y reiniciamos.
Ahora las variables están correctamente configuradas y ya podemos instalar Openbravo.

Instalación de Openbravo:

Lo primero es descargarlo, para esto nos dirigimos acá y seguimos el wizard. Una vez descargado lo ejecutamos.

Aceptamos la licencia, seleccionamos el directorio de instalación


seleccionamos el directorio de los attachments


seleccionamos la instalación completa


luego la instalación estándar


luego seleccionamos el directorio con los ejecutables de java


ant


y tomcat.


Como configuramos las variables aparecerán automáticamente. Luego seleccionamos la base de datos PostgreSQL


luego aparecerá automáticamente el directorio de los ejecutables de PostgreSQL (al instalarlo se configuraron las variables de entorno de PostgreSQL).


Luego ingresamos el host y el puerto de la base de datos


luego ingresamos la contraseña del usuario postgres


luego ingresamos el nombre de la base de datos, el usuario y su contraseña


Luego ingresamos el nombre del contexto de la aplicación


luego seleccionamos el formato de la fecha que queremos usar en el sistema


luego seleccionamos si queremos que se llene la base de datos con datos ficticios (la empresa Big Bazar).


Una vez listo comienza la instalación.


La instalación demora mas o menos una hora, dependiendo del equipo y sus recursos.
Una vez finalizado estamos listos.


Ahora para iniciar openbravo debemos tener iniciado PostgreSQL y Tomcat.
Para iniciar postgreSQL vamos a Menú inicio -> Todos los programas -> Postgresql 8.3 -> start service
Para iniciar Tomcat vamos a C:\apache-tomcat-6.0.18\bin y ejecutamos el archivo startup.bat

Luego abrimos en nuestro navegador favorito la siguiente URL: http://localhost:8080/openbravo

Ingresamos el nombre de usuario Openbravo y la contraseña openbravo


y ya estamos dentro del sistema.


No hay nada mejor que tener tu propio ERP.

Espero les haya servido esta humilde guía de instalción.

saludos !!!

No Cache, No Store, Internet Explorer en problemas !!!

miércoles, 19 de noviembre de 2008
Hace meses estoy desarrollando un sistema de administración de prácticas profesionales para la Escuela de Ingeniería Civil de la Universidad Andrés Bello (en Chile).

Resulta que lo estoy desarrollando en Java utilizando el IDE NetBeans 6.5 (partí usando el 6.0, luego el 6.1), uso JSP, Servlets, AJAX (con JMAKI), CSS2, una biblioteca para PDF llamada iText, para el almacenamiento de datos utilizo MySQL (5.0.67) y el servidor de aplicaciones es, por supuesto, Glassfish V2 UR2.

El sistema funciona correctamente corriendo en Windows o Linux. Yo utilizo Firefox y firebug no me ha encontrado ningún error en ninguna parte del sistema. El problema lo tuve al querer acceder al sistema utilizando el "navegador web" Internet Explorer (6.0, 7.0, 8.0Beta2, sobre Windows XP SP1, SP2 y SP3). Siempre que quería acceder me salia el siguiente error:
Internet Explorer cannot download index.jsp from localhost
Internet Explorer was not able to open this Internet site. The requested site is either unavailable or cannot be found.
Please try again later.
Aquí una imagen mostrando la pantalla de error en Windows XP SP3:


Esto me ha traido dolores de cabeza por bastante tiempo, busqué y busqué en Internet sin respuestas satisfactorias. De pronto llegué a una página de Microsoft donde sale el problema de "Internet explorer cannot download". Ahí sale que ese error ocurre cuando la URL es segura (HTTPS) (NO es mi caso).

Entre las causas del error sale el uso de las cabeceras "no-cache" y "no-store". Precisamente en mi sistema utilizo esas cabeceras en todos los JSP por seguridad y consistencia. En esa misma página se reconoce que el problema es de los productos Microsoft (para variar ...... ¬¬), en Internet Explorer 6.0 SP1. Aprovecho para comunicar a la gente de Microsoft que aún no solucionan el problema en todas las versiones posteriores a la 6.0 SP1.

Lo mas ridículo de todo lo que sale en esa página de Microsoft es el WORKAROUND:
To work around this problem, make sure that Do Not Save Encrypted Files check box is not checked and that the server does not send the "Cache-Control: No Store" or the "Cache-Control: No Cache" header.

Y en español dice:
Para solucionar este problema, asegúrese que la opción No Guardar Archivos Encriptados no esté marcada y que el servidor no envíe la cabecera "Cache-Control: No Store" o la "Cache-Control: No Cache".
Inteligente la solución ¬¬, si usted es un usuario, como puede hacer que el servidor no envíe esas cabeceras ?? si usted es desarrollador de software y realmente necesita que no se almacenen los datos en el cache del navegador, va a permitir que Microsoft le de una respuesta tan absurda ??

Bueno, ese es el problema. La solución no era tan complicada, tediosa si. Se debe agregar a TODOS los JSP las siguientes líneas (se agregan al principio, fuera de la etiqueta <html>):
response.setHeader("Cache-Control","no-cache");
response.setHeader("Cache-Control","no-store");
response.setHeader("Cache-Control","must-revalidate");
response.setHeader("Pragma","no-cache");
response.setDateHeader ("Expires", 0);
Para que la solución quede mas elegante se puede crear un fragmento de JSP, un archivo JSPF e insertar esas lineas ahí y luego en los JSP agregar un include que apunte al fragmento, así:
<jsp:directive.include file="../WEB-INF/jspf/NoCache.jspf">

Ya con eso el problema está solucionado, ahora el sistema funciona en Firefox e Internet Explorer.

Desgraciadamente aún hay que hacer excepciones para el navegador mas usado, pues no sigue estándares y lo que hace lo hace casi siempre mal, esta es solo una de las cosas que tuve que realizar para que el sistema funcionara con Internet Explorer, también tuve que hacerlo al crear archivos PDF para que el IE los pudiera descargar, entre otras cosas.

Ya saben, si les ocurre este problema ya tienen una solución.

saludos !!!