Подтвердить что ты не робот

Ejabberd онлайн-статус, когда пользователь теряет соединение

У меня есть настройка ejabberd как сервер xmpp между мобильными приложениями, т.е. пользовательское приложение для iPhone и Android.

Но я, похоже, столкнулся с ограничением того, как ejabberd обрабатывает онлайн-статус.

Сценарий:

  • Пользователь A является пользователем обмена сообщениями B через свои мобильные телефоны.
  • Пользователь B теряет все возможности подключения, поэтому клиент не может отключиться от сервера.
  • ejabberd по-прежнему перечисляет пользователя B как онлайн.
  • Поскольку ejabberd предполагает, что пользователь B все еще находится в сети, любое сообщение от пользователя A переходит к мертвому соединению.
  • Таким образом, пользователь B не получит сообщение и не будет сохранен как автономное сообщение, так как ejabberd предполагает, что пользователь подключен к сети.
  • Сообщение потеряно.
  • Пока ejabberd не понимает, что соединение устарело, оно рассматривает его как онлайн-пользователя.

И забросьте изменения соединения с данными (от Wi-Fi до 3G до 4G...), и вы обнаружите, что это происходит довольно много.

mod_ping:

Я попытался реализовать mod_ping на 10-секундном интервале.
https://www.process-one.net/docs/ejabberd/guide_en.html#modping
Но, как указано в документации, пинг будет ждать 32 секунды для ответа перед отключением пользователя.
Это означает, что появится 42-секундное окно, в котором пользователь может потерять свои сообщения.

Идеальное решение:

Даже если время ожидания ping может быть уменьшено, это все еще не идеальное решение.
Есть ли способ, которым ejabberd может ждать ответа 200 от клиента перед отбрасыванием сообщения? Если ответа нет, сохраните его в автономном режиме.
Можно ли написать крючок, чтобы решить эту проблему?
Или есть простая настройка, которую я где-то пропустил?

FYI: Я не использую BOSH.

4b9b3361

Ответ 1

Вот мой мод, который исправил мою проблему.

Чтобы сделать это, вам понадобятся квитанции, которые будут активированы на стороне клиента, и клиент должен иметь возможность обрабатывать повторяющиеся сообщения.

Сначала я создал таблицу под названием confirm_delivery. Я сохраняю каждое сообщение чата в этой таблице. Я установил 10-секундный таймер, если я получу подтверждение обратно, я удалю запись в таблице.

Если я не получил подтверждение обратно, я сохраняю сообщение вручную в таблице offline_msg и повторю попытку повторной отправки (это может быть сверху, но для вас нужно решить), а затем удалить его из нашей таблицы confirm_delivery

Я уничтожил весь код, который я воспринимаю как ненужный, поэтому я надеюсь, что это все равно будет компилироваться.

Надеюсь, что это поможет другим разработчикам 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).    

Ответ 2

Это хорошо известное ограничение TCP-соединений. Вам нужно ввести некоторые функции подтверждения.

Один из вариантов в xep-0184. Сообщение может нести запрос на получение, а когда оно доставлено, квитанция возвращается отправителю.

Другой вариант - xep-0198. Это управление потоками, которое подтверждает строфы.

Вы также можете полностью реализовать его на прикладном уровне и отправлять сообщения от получателя отправителю. Действуйте соответственно, когда подтверждение не доставляется. Имейте в виду, что Sender → Server connection также может быть разорвано таким образом.

Мне не известно о реализации этих xeps и функций в ejabberd. Я реализовал их самостоятельно, в зависимости от требований проекта.

Ответ 3

ejabberd поддерживает управление потоками по умолчанию в последней версии. Он реализован в большинстве мобильных библиотек, таких как Smack for Android и XMPPFramework для iOS.

Это современное состояние в спецификации XMPP на данный момент.

Ответ 4

Внедрение XEP-198 на ejabberd довольно активно.

Erlang Solutions (я работаю для них) имеет модуль XEP-184 для ejabberd с расширенными функциональными возможностями, который решает эту проблему. Он выполняет буферизацию и проверку на стороне сервера. Пока клиент отправляет сообщения, несущие запрос на получение, и когда он доставлен, квитанция возвращается отправителю.

Модуль проверяет поступления, чтобы узнать, получено ли сообщение. Если он не в течение таймаута, он сохраняется в автономном режиме.

Ответ 5

Я думаю, что лучший способ заключается в том, что если сообщение не было принято, отключите пользователя, а затем сохраните сообщение в автономной таблице сообщений и используйте услугу push и настройте ее для автономного сообщения.

Затем будет отправлено push, и если будет больше сообщений, они будут сохранены в автономном сообщении, а для понимания на сервере это сообщение не получено, вы можете использовать этот https://github.com/Mingism/ejabberd-stanza-ack.

Я думаю, что Facebook так же, когда сообщение не доставляет его, отключает пользователя, пока он не станет онлайн снова

Ответ 6

Ejabberd поддерживает управление потоками по умолчанию в последней версии. После настройки конфигурации диспетчера потоков в ejabberd_c2s, вы должны установить некоторую конфигурацию в своем клиенте. Пожалуйста, ознакомьтесь с этим сообщением для этой конфигурации на клиенте. https://community.igniterealtime.org/thread/55715