tutorial servidor erlang xmpp ejabberd

erlang - servidor - xmpp server



estado en línea ejabberd cuando el usuario pierde conexión (6)

Esta es una limitación bien conocida de las conexiones TCP. Debe introducir alguna funcionalidad de reconocimiento.

Una de las opciones en xep-0184. Un mensaje puede llevar una solicitud de recibo y, cuando se entrega, el recibo vuelve al remitente.

Otra opción es xep-0198. Esta es la gestión de flujo que reconoce las estrofas.

También puede implementarlo completamente en la capa de aplicación y enviar mensajes de destinatario a remitente. Actúe en consecuencia cuando no se entregue el reconocimiento. Tenga en cuenta que el remitente -> conexión del servidor también puede ser cortado de esa manera.

No estoy al tanto de la implementación de esos xeps y funciones en ejabberd. Los implementé yo solo según los requisitos del proyecto.

Tengo configuración ejabberd para ser el servidor xmpp entre aplicaciones móviles, es decir. aplicación personalizada de iPhone y Android.

Pero aparentemente me encontré con una limitación de la forma en que ejabberd maneja el estado en línea.

Guión:

  • El usuario A está enviando mensajes al usuario B a través de sus teléfonos móviles.
  • El usuario B pierde toda la conectividad, por lo que el cliente no puede desconectarse del servidor.
  • ejabberd todavía muestra el usuario B como en línea.
  • Dado que ejabberd supone que el usuario B aún está en línea, cualquier mensaje del usuario A pasa a la conexión muerta.
  • Entonces, el usuario B no recibirá el mensaje, ni se guardará como un mensaje fuera de línea, ya que ejabberd supone que el usuario está en línea.
  • Mensaje perdido
  • Hasta que ejabberd se dé cuenta de que la conexión está obsoleta, la trata como un usuario en línea.

Y añada cambios en la conexión de datos (wifi a 3G a 4G a ...) y verá que esto sucede bastante.

mod_ping:

Traté de implementar mod_ping en un intervalo de 10 segundos.
https://www.process-one.net/docs/ejabberd/guide_en.html#modping
Pero como dice la documentación, el ping esperará 32 segundos para obtener una respuesta antes de desconectar al usuario.
Esto significa que habrá una ventana de 42 segundos donde el usuario puede perder sus mensajes.

Solución ideal:

Incluso si el tiempo de espera del ping se puede reducir, todavía no es una solución perfecta.
¿Hay alguna manera de que ejabberd pueda esperar una respuesta de 200 del cliente antes de descartar el mensaje? Si no hay respuesta, guárdelo sin conexión.
¿Es posible escribir un gancho para resolver este problema?
¿O hay una configuración simple que me he perdido en alguna parte?

FYI: No estoy usando BOSH.


Implementar XEP-198 en ejabberd es bastante complicado.

Erlang Solutions (yo trabajo para ellos) tiene un módulo XEP-184 para ejabberd, con funcionalidad mejorada, que resuelve este problema. Hace el almacenamiento en búfer y la validación en el lado del servidor. Siempre que el cliente envíe mensajes que lleven una solicitud de recibo y cuando se entregue, el recibo vuelve al remitente.

El módulo valida los recibos para ver si se recibió el mensaje. Si no se ha agotado el tiempo de espera, se guarda como un mensaje fuera de línea.


Creo que la mejor manera es que si no se recibe un mensaje, desconecte al usuario y luego almacene el mensaje en la tabla de mensajes sin conexión y use un servicio push y configúrelo para el mensaje fuera de línea.

Luego se enviará un push y si hay más mensajes se almacenarán en un mensaje fuera de línea, y para entender en el servidor ese mensaje no se ha recibido, puede usar este https://github.com/Mingism/ejabberd-stanza-ack .

Creo que Facebook tiene la misma manera cuando un mensaje no se entrega. Hace que el usuario esté desconectado hasta que vuelva a estar conectado.


Aquí está el mod que escribí que soluciona mi problema.

Para que funcione, necesitará que los recibos se activen en el lado del cliente y que el cliente pueda manejar los mensajes duplicados.

Primero creé una tabla llamada confirm_delivery. Guardo todos los mensajes de "chat" en esa mesa. Configuré un temporizador de 10 segundos, si recibo una confirmación de regreso, elimino la entrada de la tabla.

Si no recibo una confirmación, guardo el mensaje de forma manual en la tabla offline_msg y trato de volver a enviarlo de nuevo (esto puede ser más importante, pero para que usted decida) y luego eliminarlo de nuestra tabla confirm_delivery

Corté todo el código que percibo como innecesario, así que espero que esto aún se compile.

Espero que esto sea de ayuda para otros desarrolladores de ejabberd.

https://github.com/johanvorster/ejabberd_confirm_delivery.git

%% name of module must match file name -module(mod_confirm_delivery). -author("Johan Vorster"). %% Every ejabberd module implements the gen_mod behavior %% The gen_mod behavior requires two functions: start/2 and stop/1 -behaviour(gen_mod). %% public methods for this module -export([start/2, stop/1, send_packet/3, receive_packet/4, get_session/5, set_offline_message/5]). %% included for writing to ejabberd log file -include("ejabberd.hrl"). -record(session, {sid, usr, us, priority, info}). -record(offline_msg, {us, timestamp, expire, from, to, packet}). -record(confirm_delivery, {messageid, timerref}). start(_Host, _Opt) -> ?INFO_MSG("mod_confirm_delivery loading", []), mnesia:create_table(confirm_delivery, [{attributes, record_info(fields, confirm_delivery)}]), mnesia:clear_table(confirm_delivery), ?INFO_MSG("created timer ref table", []), ?INFO_MSG("start user_send_packet hook", []), ejabberd_hooks:add(user_send_packet, _Host, ?MODULE, send_packet, 50), ?INFO_MSG("start user_receive_packet hook", []), ejabberd_hooks:add(user_receive_packet, _Host, ?MODULE, receive_packet, 50). stop(_Host) -> ?INFO_MSG("stopping mod_confirm_delivery", []), ejabberd_hooks:delete(user_send_packet, _Host, ?MODULE, send_packet, 50), ejabberd_hooks:delete(user_receive_packet, _Host, ?MODULE, receive_packet, 50). send_packet(From, To, Packet) -> ?INFO_MSG("send_packet FromJID ~p ToJID ~p Packet ~p~n",[From, To, Packet]), Type = xml:get_tag_attr_s("type", Packet), ?INFO_MSG("Message Type ~p~n",[Type]), Body = xml:get_path_s(Packet, [{elem, "body"}, cdata]), ?INFO_MSG("Message Body ~p~n",[Body]), MessageId = xml:get_tag_attr_s("id", Packet), ?INFO_MSG("send_packet MessageId ~p~n",[MessageId]), LUser = element(2, To), ?INFO_MSG("send_packet LUser ~p~n",[LUser]), LServer = element(3, To), ?INFO_MSG("send_packet LServer ~p~n",[LServer]), Sessions = mnesia:dirty_index_read(session, {LUser, LServer}, #session.us), ?INFO_MSG("Session: ~p~n",[Sessions]), case Type =:= "chat" andalso Body =/= [] andalso Sessions =/= [] of true -> {ok, Ref} = timer:apply_after(10000, mod_confirm_delivery, get_session, [LUser, LServer, From, To, Packet]), ?INFO_MSG("Saving To ~p Ref ~p~n",[MessageId, Ref]), F = fun() -> mnesia:write(#confirm_delivery{messageid=MessageId, timerref=Ref}) end, mnesia:transaction(F); _ -> ok end. receive_packet(_JID, From, To, Packet) -> ?INFO_MSG("receive_packet JID: ~p From: ~p To: ~p Packet: ~p~n",[_JID, From, To, Packet]), Received = xml:get_subtag(Packet, "received"), ?INFO_MSG("receive_packet Received Tag ~p~n",[Received]), if Received =/= false andalso Received =/= [] -> MessageId = xml:get_tag_attr_s("id", Received), ?INFO_MSG("receive_packet MessageId ~p~n",[MessageId]); true -> MessageId = [] end, if MessageId =/= [] -> Record = mnesia:dirty_read(confirm_delivery, MessageId), ?INFO_MSG("receive_packet Record: ~p~n",[Record]); true -> Record = [] end, if Record =/= [] -> [R] = Record, ?INFO_MSG("receive_packet Record Elements ~p~n",[R]), Ref = element(3, R), ?INFO_MSG("receive_packet Cancel Timer ~p~n",[Ref]), timer:cancel(Ref), mnesia:dirty_delete(confirm_delivery, MessageId), ?INFO_MSG("confirm_delivery clean up",[]); true -> ok end. get_session(User, Server, From, To, Packet) -> ?INFO_MSG("get_session User: ~p Server: ~p From: ~p To ~p Packet ~p~n",[User, Server, From, To, Packet]), ejabberd_router:route(From, To, Packet), ?INFO_MSG("Resend message",[]), set_offline_message(User, Server, From, To, Packet), ?INFO_MSG("Set offline message",[]), MessageId = xml:get_tag_attr_s("id", Packet), ?INFO_MSG("get_session MessageId ~p~n",[MessageId]), case MessageId =/= [] of true -> mnesia:dirty_delete(confirm_delivery, MessageId), ?INFO_MSG("confirm_delivery clean up",[]); _ -> ok end. set_offline_message(User, Server, From, To, Packet) -> ?INFO_MSG("set_offline_message User: ~p Server: ~p From: ~p To ~p Packet ~p~n",[User, Server, From, To, Packet]), F = fun() -> mnesia:write(#offline_msg{us = {User, Server}, timestamp = now(), expire = "never", from = From, to = To, packet = Packet}) end, mnesia:transaction(F).


ejabberd admite la administración de secuencias como predeterminado en la última versión. Se implementa en la mayoría de las bibliotecas móviles como Smack para Android y XMPPFramework para iOS.

Este es el estado de la técnica en la especificación XMPP en este momento.


Ejabberd admite la gestión de flujo como predeterminado en la última versión. Después de establecer la configuración del gestor de flujo en ejabberd_c2s, debe establecer algunas configuraciones en su cliente. Por favor, mira esta publicación para esta configuración en el cliente. https://community.igniterealtime.org/thread/55715