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:
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:
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:
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:
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:
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:
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:
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.
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:
o ejecutar en Linux
o en Windows (se debe renombrar el archivo run.bat.txt a run.bat)
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:
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:
Saludos !!!
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 servidorMediante 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.
-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)
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.batNo 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 !!!