异常错误:Mochiweb / Erlang中的未定义函数

时间:2009-06-11 16:56:35

标签: erlang mochiweb

看到this article后,我一直在修补mochiweb。试图复制文章中的内容 - 基本上设置一个mochiweb服务器,有两个erlang节点,然后在另一个节点中调用一个节点中定义的函数(在两个节点之间设置net_adm:ping()之后,让他们知道每个节点其他)。

我能够跟踪所有功能,直到该功能调用部分。在n1 @ localhost,这是mochiweb服务器,我打电话(正如文章中所做的那样):

router:login(IdInt, self()).

然后,在n2 @ localhost中,这是router.erl脚本,我已经定义了登录功能:

login(Id, Pid) when is_pid(Pid) ->
    gen_server:call(?SERVER, {login, Id, Pid}).

handle_call({login, Id, Pid}, _From, State) when is_pid(Pid) ->
          ets:insert(State#state.pid2id, {Pid, Id}),
          ets:insert(State#state.id2pid, {Id, Pid}),
          link(Pid), % tell us if they exit, so we can log them out
          io:format("~w logged in as ~w\n",[Pid, Id]),
          {reply, ok, State};

我只粘贴了代码的相关部分。但是,当我现在访问浏览器上的Web服务器时 - 我在n1 @ localhost:

上收到此错误报告
=CRASH REPORT==== 11-Jun-2009::12:39:49 ===
  crasher:
    initial call: mochiweb_socket_server:acceptor_loop/1
    pid: <0.62.0>
    registered_name: []
    exception error: undefined function router:login/2
      in function  mochiconntest_web:loop/2
      in call from mochiweb_http:headers/5
    ancestors: [mochiconntest_web,mochiconntest_sup,<0.59.0>]
    messages: []
    links: [<0.61.0>,#Port<0.897>]
    dictionary: [{mochiweb_request_path,"/test/123"}]
    trap_exit: false
    status: running
    heap_size: 1597
    stack_size: 24
    reductions: 1551
  neighbours:

=ERROR REPORT==== 11-Jun-2009::12:39:49 ===
{mochiweb_socket_server,235,{child_error,undef}}

在谷歌搜索之后,我得到了错误试图说明的基本要点 - 基本上它表示在n1 @ localhost中调用的登录函数未定义 - 但它在n2 @ localhost中定义(并且两者都是节点互相认识 - 我做nodes().检查)!!请告诉我哪里出错了!

2 个答案:

答案 0 :(得分:1)

你是对的 - 路由器的代码:你的主机上实际上没有登录n1 @ localhost - 它是该函数中的代码(gen_server:call函数),它将调用路由到n2 @ localhost(通过它? SERVER宏)这就是真正实现的地方。顶级函数只是将调用包装到适当节点的一种方式。

但您至少需要登录

的实施
login(Id, Pid) when is_pid(Pid) ->
    gen_server:call(?SERVER, {login, Id, Pid}).

在n1 @ localhost上可用。

(更新)

您还需要定义,替换或使用?SERVER宏。在本文的示例代码中,这是

-define(SERVER, global:whereis_name(?MODULE)).

但这会使用?MODULE宏,这在你的情况下是错误的。基本上,当gen_server进程(路由器)启动时,它将自己注册为?MODULE,在这种情况下映射到其他节点可以看到的原子'router'(使用global:whereis_name(router))。所以你应该只能写:

login(Id, Pid) when is_pid(Pid) ->
    gen_server:call(global:whereis_name(router), {login, Id, Pid}).

因此,在n1 @ localhost上调用login的效果将对n2 @ localhost上的路由器:handle_call方法进行gen_server调用,假设路由器gen_server进程正在运行并已自行注册。该调用的返回值将返回到n1 @ localhost上的进程。

答案 1 :(得分:0)

在您的问题的示例中,您似乎只在一个节点上加载了router模块。节点默认情况下不会自动加载彼此的代码,所以只是因为函数在n2上定义并不意味着你可以在n1上本地调用它(n1需要能够以正常方式加载它)。

给出的代码看起来像在分布式系统中运行一样正确(您可以在一个节点上启动路由器服务器并在其他节点上调用API函数将路由器请求发送到正确的位置)。所以你只需要在n1上放一个router模块的副本就可以了。有可能让n1从n2加载router模块,但与在代码路径中给n1模块的副本相比,这有点麻烦。

有趣的是,在路由器代码中,没有必要执行gen_server:call(global:whereis_name(?MODULE, Message)).,因为gen_server:call/2函数知道如何查找全局注册本身。如果您将start_link函数更改为-define(SERVER, {global, ?MODULE}).

start_link() -> gen_server:start_link(?SERVER, ?MODULE, [], []).可以正常工作