IntroducciónHace 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ísticasLa aplicación que desarrollé soporta:
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.
MensajesLos 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íaEl 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 internoFormateando el mensajePara 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 servidorPara 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 mensajePara 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ónPara 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 pantallaAquí 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-2008He 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-2009Hasta 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-2009He 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 !!!