【问题标题】:ejabberd online status when user loses connection用户断开连接时的 ejabberd 在线状态
【发布时间】:2013-06-29 17:48:23
【问题描述】:

我已将 ejabberd 设置为移动应用程序之间的 xmpp 服务器,即。定制 iPhone 和 Android 应用。

但我似乎遇到了 ejabberd 处理在线状态的方式的限制。

场景:

  • 用户 A 正在通过手机向用户 B 发送消息。
  • 用户 B 失去所有连接,因此客户端无法断开与服务器的连接。
  • ejabberd 仍将用户 B 列为在线。
  • 由于 ejabberd 假定用户 B 仍然在线,因此来自用户 A 的任何消息都会传递到死连接。
  • 因此用户 B 不会收到消息,也不会将其保存为离线消息,因为 ejabberd 假定用户在线。
  • 消息丢失。
  • 在 ejabberd 意识到连接失效之前,它会将其视为在线用户。

加上数据连接变化(wifi 到 3G 到 4G 到...),你会发现这种情况经常发生。

mod_ping:

我尝试每隔 10 秒执行一次 mod_ping。
https://www.process-one.net/docs/ejabberd/guide_en.html#modping
但正如文档所述,ping 将在断开用户连接之前等待 32 秒的响应。
这意味着会有一个 42 秒的窗口,用户可以在其中丢失他们的消息。

理想的解决方案:

即使可以减少 ping 等待时间,它仍然不是一个完美的解决方案。
有没有办法让 ejabberd 在丢弃消息之前等待来自客户端的 200 响应?如果没有响应,则离线保存。
是否可以写一个钩子来解决这个问题?
或者我在某处错过了一个简单的设置?

仅供参考:我没有使用 BOSH。

【问题讨论】:

  • 不是一个完整的答案,但可能对某人有用:我已经通过使用带有(社区贡献的)XEP-198 插件(称为 smacks)的 prosody jabber 服务器解决了一个问题。 Wikipedia lists several other servers with support for 198,但韵律只是默认 Debian 存储库中的一种。在(Android)客户端,我使用了 Yaxim

标签: erlang xmpp ejabberd


【解决方案1】:

Ejabberd 在最新版本中默认支持流管理。 在 ejabberd_c2s 中设置流管理器配置后,您应该在客户端中设置一些配置。 请参阅此帖子以了解客户端中的此配置。 https://community.igniterealtime.org/thread/55715

【讨论】:

    【解决方案2】:

    ejabberd 在最新版本中默认支持流管理。它在大多数移动库中实现,例如 Android 的 Smack 和 iOS 的 XMPPFramework。

    这是目前 XMPP 规范中的最新技术。

    【讨论】:

    • 干得好!!也感谢你的团队!!我相信很多人都在等待这个功能!
    【解决方案3】:

    这是我写的修复我的问题的模组。

    要使其正常工作,您需要在客户端激活收据,并且客户端应该能够处理重复的消息。

    首先,我创建了一个名为 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).    
    

    【讨论】:

    • ,感谢上面的代码,你能告诉我这个模块是否可以与 ejabberd 2.1.11 一起工作??我应该如何编译这个模块??说如何创建 ejabberd_confirm_delivery.beam?希望你很快就会重播!
    • 是的,这适用于 ejabberd 2.1.11。运行 erlang shell,指向保存文件的目录并使用 c(mod_confirm_delivery)。命令。这应该会为您生成一个梁文件。 erlang.org/documentation/doc-5.3/doc/getting_started/…
    • @JohanVorster 我尝试了这段代码,但我遇到了错误。prntscr.com/5jt2hi 我该如何解决它们?
    • @TolgayToklar 不幸的是,我没有 erlang/ejabberd 环境可供我使用了。所以不可能检查发生了什么。你从哪里编译?尝试从 ebin 文件夹编译它。
    • 似乎 ejabberd 从 13.10 版开始更改了 INFO_MSG 宏和 send_packet 参数。包括'-include("logger.hrl")。'并确保从源代码(ebin)编译。或者删除 INFO_MSG 方法
    【解决方案4】:

    我认为更好的方法是,如果没有收到消息,让用户离线,然后将消息存储在离线消息表中,并使用推送服务并将其配置为离线消息。

    然后将发送推送,如果有更多消息,它们将存储在离线消息中,为了在服务器上了解该消息尚未收到,您可以使用此https://github.com/Mingism/ejabberd-stanza-ack

    我认为 Facebook 有同样的方式,当消息没有传递时,它会使用户离线,直到他再次在线

    【讨论】:

    • Johan Vorster 很棒的模块。我们在 ejabberd 14.12 上安装了它,但没有用。我们应该做些什么改变才能让它在 ejabberd 14.12 上工作
    • ejabberd 似乎从 13.10 版本更改了 INFO_MSG 宏和 send_packet 参数。
    【解决方案5】:

    在 ejabberd 上实现 XEP-198 非常复杂。

    Erlang Solutions(我为他们工作)有一个用于 ejabberd 的 XEP-184 模块,具有增强的功能,可以解决这个问题。它在服务器端进行缓冲和验证。只要客户端发送带有回执请求的消息,并且当它被传递时,回执就会返回给发送者。

    模块验证收据以查看是否已收到消息。如果它没有在超时内,它会被保存为离线消息。

    【讨论】:

    • 我们使用收据 (XEP-184) 来验证接收者是否收到了消息。但是,如果消息丢失或离线保存,发件人通常不会更明智。
    • 我创建了一个模组并连接到 send_packet 和 receive_packet 事件。将消息 ID 保存到表中。启动一个 10 秒的等待线程。如果 receive_packet 钩子在 10 秒内获得消息 ID,我会终止线程,否则我会手动将消息存储在离线表中。现在最坏的情况是,我可能在脱机表中有两次味精。但它将具有相同的 ID,我们的客户知道不要重复消息。
    • @JohanVorster 嗨,Johan,我遇到了与 ejabberd 类似的问题。你会考虑分享你的模组吗?我会很感激,它会为我节省很多时间。谢谢!
    • @ChrisMcCabe 我已将代码添加为答案,看看是否适合您。
    • @JohanVorster 谢谢约翰,我很感激!
    【解决方案6】:

    这是众所周知的 TCP 连接限制。您需要引入一些确认功能。

    xep-0184 中的选项之一。一条消息可以携带回执请求,当它被传递时,回执会返回给发送者。

    另一个选项是 xep-0198。这是确认节的流管理。

    您也可以完全在应用层实现它,并将消息从收件人发送到发件人。 在未发送确认时采取相应的行动。 请注意,Sender -> Server 连接也可能以这种方式被切断。

    我不知道 ejabberd 中这些 xeps 和功能的实现。我根据项目要求自己实现了它们。

    【讨论】:

    • 很遗憾 ejabberd 不支持 xep-0198。我们已经实现了 xep-0184,但是 ejabberd 服务器实际上并没有验证收据,它只是将它传回给发件人。因此没有服务器验证来查看是否已收到消息。在发送消息之前,我可能需要每次 ping 客户端,看看它们是否仍然连接。这实际上可能比每 10 秒 ping 所有连接的客户端的开销要少。
    • 我同意约翰·沃尔斯特的观点
    猜你喜欢
    • 1970-01-01
    • 2016-03-03
    • 2015-09-18
    • 2016-10-02
    • 2017-04-21
    • 1970-01-01
    • 2015-05-14
    • 2021-07-06
    • 2020-11-30
    相关资源
    最近更新 更多