featured image

Al construir nuestro sistema de chat nos encontramos ante un problema: los dispositivos móviles no son confiables. No mantienen una conexión constante a Internet y pierden la conexión fácilmente. Por otro lado XMPP está ideado para conexiones permanentes y confiables a Internet. ¿Cómo podemos usarlo sin perder información?

En principio vamos a fijar nuestra atención en Whatsapp (como dice el nombre del artículo) y por lo tanto tendremos en cuenta:

Whatsapp SOLO puede estar activo con la misma cuenta en un mismo dispositivo. Si instalas Whatsapp en dos dispositivos uno desconectará a otro o incluso invalidará la instalación de otro. E incluso si usamos la aplicación de Whatsapp esta aplicación requiere de conexión con la app del móvil. En resumen SOLO un dispositivo por cuenta.

Sé que muchas otras aplicaciones son multi-dispositivo permitiendo la coexistencia de varios dispositivos, pero esto requiere de otras técnicas adicionales a las que comentaré en este artículo y requeriría otro artículo completamente nuevo.

Mensajes

Tener dos dispositivos conectados y enviar mensajes entre ellos es el caso de uso ideal de XMPP. Ambos dispositivos están conectados al servidor y todo lo que envía un dispositivo llega al otro dispositivo inmediatamente. No hay mucho secreto en esto.

Si un dispositivo desconecta el servidor cierra su conexión y cualquier nuevo mensaje lo envía a un nuevo flujo de fuera de línea (o en inglés offline). Una vez vuelva a conectar el cliente volverá a obtener de nuevo todos esos mensajes.

Pero si perdemos la conexión o conectividad en un momento dado, ¿qué ocurre?

En principio si un dispositivo móvil pierde cobertura, se apaga por falta de batería o incluso la aplicación es cerrada por estar en segundo plano en todos estos casos suele producirse una desconexión incorrecta a nivel del protocolo TCP. El servidor aún considera la conexión establecida hasta que se produzca un envío usando ese canal y produzca un tiempo de espera agotado (timeout) por no recibir respuesta alguna.

Existen varias técnicas para paliar esta situación, veremos algunas.

Enviando ping

El XEP-0199 nos da las pautas a seguir para implementar el sistema de ping. Este sistema se basa en el envío periódico por parte del cliente y/o servidor de un paquete específico a espera de una respuesta.

El paquete es como este:

<iq from='capulet.lit' to='juliet@capulet.lit/balcony' id='s2c1' type='get'>
  <ping xmlns='urn:xmpp:ping'/>
</iq>

El servidor de nombre capulet.lit envía a juliet@capulet.lit/balcony un mensaje para saber si sigue conectado correctamente o no. En caso de que siga ahí el cliente debería responder:

<iq from='juliet@capulet.lit/balcony' to='capulet.lit' id='s2c1' type='result'/>

La configuración típica del servicio de ping establece enviar cada 60 segundos este tipo de mensajes. Si el cliente envía el ping al servidor el servidor responde y reinicia el contador para el envío del siguiente mensaje de ping.

Las ventajas de este sistema es comprobar si el cliente aún sigue ahí por parte del servidor y de parte del cliente saber si aún tiene conectividad con el servidor.

Las desventajas son el tiempo. Esta prueba se realiza cada 60 segundos o algo menos si lo configuramos con más frecuencia pero aún así nos deja un tiempo de incertidumbre. Podríamos perder mensajes en ese tiempo. Si incrementamos mucho la frecuencia podríamos incrementar también innecesariamente el tráfico de red al cliente.

Acuses de Recibo

Otra de las técnicas es el envío de acuses de recibo. Esta técnica no se basa en saber si el usuario está conectado o no, se basa en intentar detectar si el mensaje fue recibido o no por la otra persona.

Está cubierto por el XEP-0184. Se basa en solicitar el envío de un acuse de recibo en cada mensaje enviado:

<message
    from='northumberland@shakespeare.lit/westminster'
    id='richard2-4.1.247'
    to='kingrichard@royalty.england.lit/throne'>
  <body>My lord, dispatch; read o'er these articles.</body>
  <request xmlns='urn:xmpp:receipts'/>
</message>

Este mensaje enviado por northumberland a kingrichard agrega la etiqueta request indicando al destinatario que responda enviando un mensaje como el siguiente:

<message
    from='kingrichard@royalty.england.lit/throne'
    id='bi29sg183b4v'
    to='northumberland@shakespeare.lit/westminster'>
  <received xmlns='urn:xmpp:receipts' id='richard2-4.1.247'/>
</message>

En la etiqueta received contenida dentro del mensaje (sin type ya que es un mensaje especial sin body) identifica al mensaje original a través del atributo id.

La ventaja de este sistema es poder tener la certeza de saber si un mensaje llegó al destinatario o no. La gran desventaja es que este mensaje es descartado por defecto por el sistema fuera de línea. Esto implica que ambos usuarios deben estar en línea cuando se envía este mensaje o un cambio en el servidor debe ser realizado para almacenarlos y entregarlos más tarde.

Otro punto negativo es que incluso el acuse de recibo podría perderse.

Administración de flujo

La siguiente técnica es más reciente y promete resolver todos estos problemas. Se especifica en el XEP-0198. Esta técnica se basa en hacer un seguimiento a cada envío y no lanzar el siguiente mensaje o trozo de XML hasta no confirmar la recepción del trozo anterior.

Del mismo modo la gestión del inicio de sesión del usuario se simplifica para acelerar la disponibilidad del usuario en caso de un micro-corte.

Por lo tanto, en el momento de enviar este trozo de XML:

<enable xmlns='urn:xmpp:sm:3'/>

Por parte del cliente queda informado al servidor la intención de usar la característica. El servidor deberá responder:

<enabled xmlns='urn:xmpp:sm:3' id='some-long-sm-id' resume='true'/>

El uso de los atributos id y resume es opcional y se basa en la característica de retomar una sesión anterior en caso de que exista.

El cliente entonces puede enviar en cualquier momento. Idealmente tras cada envío de mensaje, lo siguiente:

<r xmlns='urn:xmpp:sm:3'/>

El servidor responderá con:

<a xmlns='urn:xmpp:sm:3' h='1'/>

El atributo h nos dará la información necesaria del número del último trozo XML procesado por el servidor.

El punto positivo de esta técnica es la posibilidad de controlar el flujo punto a punto y el incremento de velocidad en micro-cortes o reconexiones. El punto negativo es la veracidad de la información. Ante una desconexión el sistema mantendrá durante un tiempo el estado en línea del usuario y no lo cerrará hasta que expire el tiempo especificado generando la sensación de que el usuario está aún en línea.

Igualmente la detección de falta de conexión debe aún cubrirse a través de ping. Este protocolo no detecta si el usuario sigue ahí o no se basa y enfoca únicamente en no perder ningún mensaje.

Sistemas de archivo

Por último, comentar dos sistemas de archivo disponibles según el servidor que usemos. MongooseIM se basa en el XEP-0313 y ejabberd se basa en el XEP-0136. Ambos cumplen la misión para la que fueron diseñados y su funcionamiento se basa en almacenar los últimos mensajes de cada usuario.

Este sistema permite acceder siempre a los últimos mensajes incluso cuando se cambia de dispositivo o si el dispositivo no tiene capacidad de almacenamiento. Tienen sistema de paginación y pueden descargar los últimos mensajes únicamente en cada reconexión o en cualquier momento e incluso solo los últimos mensajes de una determinada conversación.

El uso de estos sistemas suele dejar obsoleto el uso del sistema de almacenamiento fuera de línea (u offline), no obstante estos protocolos no contemplan la modificación de los mensajes más allá de su eliminación. Agregar capacidades de indicar si ha sido leído o no requeriría cambios en el mismo.

Sin embargo, Whatsapp no usa ninguno de estos elementos. Ellos prefieren no almacenar mensajes más allá del uso de sistema de fuera de línea. Una vez el mensaje llega al dispositivo prefieren eliminarlo de sus servidores. Es por ello por lo que requieren de acceso al dispositivo desde la aplicación web o de escritorio para poder obtener los mensajes anteriores.

Conclusiones

Mantener la conexión del usuario y garantizar la recepción de los mensajes es una tarea ardua y requiere del uso de varias de las herramientas citadas más arriba. Las más empleadas son el ping y el sistema de administración de flujo. Eso sí, requiere de bastantes pruebas con la aplicación cliente para ajustar los tiempos de inactividad y garantizar una buena experiencia de usuario.

¿Has probado el envío y recepción de mensajes?, ¿has encontrado algún problema de pérdida de mensajes?, ¿empleaste alguna de las soluciones mencionadas?, ¿aún sigues teniendo problemas y necesitas de ayuda? ¡Déjanos un comentario!