如何在Erlang中创建全局变量

16
我正在编写一个ejabberd模块来过滤数据包。我需要获取主机名,以便使用gen_mod:get_module_opt()获取一些配置信息。
我有4个重要函数:
1. start(Host, _Opt) :这是一个ejabberd函数,用于加载我的模块。我在这里获取Host原子 2. filter_packet({From, To, XML}):这是我的数据包过滤钩子。由于它是ejabberd中的钩子,因此无法向此函数传递自定义参数 3. get_translation(XmlData):循环调用filter_packet()时会调用get_translation() 4. fetch_translation(XmlData):从get_translation()递归调用。这是我调用gen_mod:get_module_opt()的位置,因此需要Host 我的问题是,如何将Hoststart()中带入全局变量,以便fetch_translation可以访问它?
8个回答

9

这可能听起来有些过度,但您可以考虑实现一个非常基本的gen_server。它包含一个状态,可用于其回调函数,并且数据可以在其中保留。对于您的情况,您可以编写一个类似于以下模块的模块:

-module(your_module_name).

-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-export([start/2, filter_loop/1]).

start(Host, Opt) ->
  %% start the named gen server
  gen_server:start({local, ?MODULE}, ?MODULE, Host, []).

filter_packet({From, To, XML}) ->
  %% do your thing
  gen_server:call(?MODULE, {fetch_translation, XmlData}).

%% this will be called by gen_server:start - just pass the Host
init(Host) ->
  {ok, Host}.

handle_call({fetch_translation, XmlData}, _From, Host) ->
  %% do your thing
  {reply, ok, Host}.

%% you can ignore the rest - they are needed to be present
handle_cast(_Msg, State) ->
  {noreply, State}.
handle_info(_Info, State) ->
  {noreply, State}.
code_change(_OldVsn, State, _Extra) ->
  {ok, State}.

9
"最简单的方法"是创建一个命名的ets表,并将其放入其中。
start(Host, _Opt) ->
  ets:new(my_table, [named_table, protected, set, {keypos, 1}]),
  ets:insert(my_table, {host, Host}),
  ...

fetch_translation(XmlData) ->
  [{_, Host}] = ets:lookup(my_table, host),
  ...

请注意,这是一个“通用”解决方案。Ejabberd可能提供了您想要的功能,但我无法帮助您处理此问题。

模块编译正常,但对于“ets:new(my_table,[named_table,protected,set,{keypos,1}])”会出现“badarg”错误。谢谢 Zed。 - Adil
如果数据表'my_table'存在,则会收到一个badarg错误。您需要检查该表是否已存在,或将其包装在try-catch块中。 - Zed
除了上面提供的代码之外,我没有在任何地方创建“my_table”。 - Adil
我来晚了,但我不会这样做,因为它会产生不必要的开销。最简单的解决方案是使用To#jid.lserver,如果你需要更多的东西,你可以将其实现为gen_server。 - Michael Weibel
这是比使用OTP进程更好的解决方案,后者会序列化每次调用并且速度较慢。 - Howy

2
你可以在模块顶部定义全局变量,例如下面这样:

你可以在模块顶部定义全局变量,例如下面这样:

-define (Your Variable, "your host name here").

eg.

-define (RelayHost, "smtp.gmail.com").

你可以在模块的所有方法中使用这个全局变量。

io:fwrite("Global Value ~p", [?RelayHost]).

-AjAy


7
define并不定义变量,它定义编译时常量。 - Kevin Albrecht
1
无法定义全局变量。 - Kevin Albrecht

1

您可以启动一个新的消息过滤进程并使用erlang:register/2进行注册,然后通过它路由所有的filter_packet/1请求(可能会成为瓶颈)。

-define(?SERVER, msg_filter).

start(Host, Opt) ->
   {ok, Pid} = spawn(?MODULE, filter_loop, [Host, Opt]),
   register(?SERVER, Pid).

filter_loop(Host, Opt) ->
   receive
      {Pid, filter_packet, {_From, _To, XML}} ->
           Trans = get_translation(XML, Host),
           Pid ! {?SERVER, translation, Trans}, 
           filter_loop(Host, Opt)
   end.

filter_packet(Pack) ->
   ?SERVER ! {self(), filter_packet, Pack}
   receive 
      {?SERVER, translation, Trans} ->
           % wrap translation
           UpdatedPacket
   end.

1

根据您的描述,我猜测您正在进行单域ejabberd部署(没有虚拟主机)。

您可以使用?MYNAME宏(请参阅ejabberd.hrl的定义)获取本地XMPP域。


1

假设您正在过滤传入的数据包,那么To#jid.lserver可能是您的主机。


嗨andi5。你能解释一下"To#jid.lserver"是什么意思吗?我该如何设置/获取它? - Adil
这只是最好的猜测,但我认为To是绑定到类型jid的记录的变量(请参见src/jlib.hrl中的记录定义,在底部)。To#jid.lserver表示您要访问记录的lserver字段,其中lserver是jid域的小写版本。如果您在shell中遇到问题,请运行rd(jid,{user,[...]})。 - andi5

1
尝试使用persistent_term
1> persistent_term:put(hello, <<"world">>).
ok
2> persistent_term:get(hello).       
<<"world">>
3> persistent_term:erase(hello).
true
4> persistent_term:get(hello).  
** exception error: bad argument
     in function  persistent_term:get/1
        called as persistent_term:get(hello)


0

你不能创建全局变量,但是你可以在函数外定义一个记录,并创建具有属性的该记录的实例,然后将其传递给你调用的方法。因此,你只能通过方法参数共享一个记录。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接