0

我在 linux 机器上使用源代码中的 ejabberd-17.03。

我使用用户 A 的 jid 从服务器以编程方式创建了一个临时聊天室,并向用户 B 发送直接邀请,他接受并加入聊天室。

我的用例是两个用户 A 和 B 在聊天室交换消息。如果在 30 秒内没有用户向其他用户发送任何消息,则房间会向这两个用户发送随机选择的消息。

我已经实现了如下:

start(_Host, _Opts) ->
   ejabberd_hooks:add(user_send_packet, _Host, ?MODULE, myMessage, 95).

stop(_Host) ->
   ejabberd_hooks:delete(user_send_packet, _Host, ?MODULE, myMessage,95).

depends(_Host, _Opts)->[{?MODULE,soft}].

mod_opt_type(_Option)->
   ok.

myMessage({#message{from = From, to = To, body= Body} =Packet, C2SState}) ->
   {UserA,UserB}=select_user(Packet),
   PacketType=returnPacketType(Packet),
   if
      (PacketType==normal) ->
         dosomething(),
         {Timer_Result,Ref_or_Reason} = timer:apply_interval(30000, ?MODULE, func(), [Arguments]),
         if
            (Timer_Result == ok)->
               ets:insert(ref_table, {Key, Ref_or_Reason});
            (Timer_Result == error)->
               io:format(" Could not delete user after timeout, Reason is ~p~n",[Ref_or_Reason])
         end;
      (PacketType==groupchat)->
         do_something_else(),
         {Timer_Result,Ref_or_Reason} = timer:apply_interval(30000, ?MODULE, func(), [Arguments]),
         if
            (Timer_Result == ok)->
               replace_old_ref_with_new(Key, Ref_or_Reason, ref_table);
            (Timer_Result == error)->
               io:format(" Could not delete user after timeout, Reason is ~p~n",[Ref_or_Reason])
         end
   end
   if
      (somecondition()==true)->
         delete_ref(Key, ref_table);
      True->
         do_nothing
   end,    
   {Packet, C2SState}.

现在一切正常,除了以下情况:

1.Chatroom 已创建,A 和 B 之间开始交换消息,此时计时器也开始。

  1. 如果创建聊天室的用户在时间 T 下线(通过最小化应用程序并从 android 设备中杀死它)并重新上线,则计时器停止,如计划在 30 秒结束时调用的函数一样't 被调用。(此处重新联机突出显示,因为如果用户未联机,则计时器按预期工作,只有当用户再次联机时,计时器停止并且不生成日志)。

    但是,如果在线用户现在在 T 点发送任何消息,那么随机选择消息并将它们发送给客户端的整个周期性操作会再次很好地开始并很好地结束。

    但是如果在线用户在这个时间点 T 没有发送任何消息,那么调度的计时器永远不会被调用,用户将继续等待。

  2. 如果被邀请加入聊天室的用户在时间 T2 下线(比如 2 中的方式)并再次上线,则计时器保持活动状态并按预期工作。

因此,我将 ejabberd 的日志记录级别更改为 5,并看到离线消息未传递给离线和再次在线用户。即使在 ejabberd.yml 中启用了 mod_offline。

日志 :

#message{
    id = <<>>,type = error,lang = <<"en">>,
    from = 
        {jid,<<"fWiTvj973AB”&gt;>,<<“example.com">>,<<"Smack">>,<<"fwitvj973ab”&gt;>,
            <<"example.com">>,<<"Smack">>},
    to = 
        {jid,<<"ac5a6b8c-66b8-4da7-8b1a-0f3ecb1e5gfd”&gt;>,
            <<"conference.example.com">>,<<"cXWmOrqEESd”&gt;>,
            <<"ac5a6b8c-66b8-4da7-8b1a-0f3ecb1e5gfd">>,
            <<"conference.example.com">>,<<"cXWmOrqEESd">>},
    subject = [#text{lang = <<>>,data = <<>>}],
    body = [#text{lang = <<>>,data = <<"\"cXWmOrqEESd\"">>}],
    thread = undefined,
    sub_els = 
        [{xmlel,<<"q">>,[{<<"xmlns">>,<<"ns:custom”&gt;>}],[]},
         #stanza_error{
             type = cancel,code = 503,by = <<>>,
             reason = 'service-unavailable',
             text = 
                 #text{lang = <<"en">>,data = <<"User session terminated">>},
             sub_els = []}],
    meta = #{}}

ejabberd.yml

###.  ============
###'  SHAPER RULES

shaper_rules:
  ## Maximum number of offline messages that users can have:
  max_user_offline_messages:
    - 5000: admin
    - 100

###.  =======
###'  MODULES

##
## Modules enabled in all ejabberd virtual hosts.
##
modules:
 mod_offline:
    db_type: sql
    access_max_user_messages: max_user_offline_messages
    store_empty_body: unless_chat_state

虽然我不需要完美地传递这些离线消息,但我倾向于认为这是否可能是停止我的计时器的原因(但我不明白为什么它只在创建的用户时停止房间下线然后又回来了,当其他用户这样做时为什么不呢?)。

为什么这个计时器会停止,我怎样才能让它定期运行?

4

2 回答 2

1

这在模块文档的timer最底部提到:

间隔计时器,即通过评估任何函数apply_interval/4、、send_interval/3和创建的计时器,send_interval/2链接到计时器执行其任务的进程。

所以timer:apply_interval将定时器服务器链接到启动定时器的进程,当调用进程退出时定时器将被取消。

显然,计时器是从管理用户连接的进程中创建的,因此当用户断开连接时,计时器会自动取消。

您可以通过生成一个管理此计时器的长时间运行的进程来解决此问题。


一个不相关的风格问题:在 Erlang 中,case通常比if. 这段代码:

 {Timer_Result,Ref_or_Reason} = timer:apply_interval(30000, ?MODULE, func(), [Arguments]),
 if
    (Timer_Result == ok)->
       ets:insert(ref_table, {Key, Ref_or_Reason});
    (Timer_Result == error)->
       io:format(" Could not delete user after timeout, Reason is ~p~n",[Ref_or_Reason])
 end;

可以写成:

 case timer:apply_interval(30000, ?MODULE, func(), [Arguments]) of
    {ok, Ref} ->
       ets:insert(ref_table, {Key, Ref});
    {error, Reason} ->
       io:format(" Could not delete user after timeout, Reason is ~p~n",[Reason])
 end;
于 2018-08-22T13:45:54.257 回答
1

我建议阅读 mod_muc_room.erl 代码并使用 muc 的钩子。例如,当用户仅在聊天室中发送消息(muc_filter_message 挂钩)或用户在聊天室(muc_filter_presence 挂钩)中发送出席信息(加入、离开等)时,您会收到通知。最好有一个处理定时器的进程(如 mod_ping)。但是对于大规模,您必须使用 ejabberd_c2s 的 c2s_handle_info 和 c2s_terminate 挂钩来管理计时器。因此,我还建议升级到 Ejabberd 18.06 或至少 17.11 。

于 2018-08-22T23:46:38.970 回答