【问题标题】:gen_server:reply/2: format of message sent to clientgen_server:reply/2: 发送给客户端的消息格式
【发布时间】:2019-11-06 12:59:10
【问题描述】:

当我打电话给gen_server:reply/2

gen_server:reply(From, Msg),

客户端From 收到一条消息,格式如下:

{Ref, Msg)

我找不到gen_server:reply/2 发送的消息格式的任何文档,我想知道如何在消息中匹配Ref。目前,我对Ref 使用无关变量:

receive
    {_Ref, Msg} -> Msg;
    Other -> Other
end

这意味着gen_server 以外的进程可能会向我的客户发送一条与{_Ref, Msg} 子句匹配的消息。

【问题讨论】:

  • 如果您使用自己的receive,为什么还要使用gen_server?我很确定gen_server 使用的消息格式是一个未记录的实现细节。

标签: erlang erlang-otp gen-server


【解决方案1】:

这是 gen_* 行为使用的gen.erl 功能。你可以看到gen_event's callgen_server's callgen_statem's call
那么它是如何工作的呢?
这个想法很简单,当您调用gen:call/4gen:call(Process, Label, Request, Timeout) 时,它会监控Process。所以erlang:monitor/2 产生一个参考。它使用这个引用并以{Label, {self(), Ref}, Request} 的形式向Process 发送消息。之后它等待{Ref, Reply} 指定Timeout 并在收到回复后恶魔Process。此外,如果Process 在发送Reply 期间崩溃,或者即使Process 在调用之前是一个死PID,它也会收到{'DOWN', Ref, _, _, Reason}

例如gen_server:call/2-3 调用gen:call(Prpcess, '$gen_call', Req, Timeout)。当服务器Process(它是一个gen_server)接收到它时,它假设它是一个调用请求,所以调用你的handle_call函数等等。

【讨论】:

  • 我认为答案——非常好——应该明确地说 gen.erl 是一个内部帮助模块,而不是“官方”OTP 发布接口的一部分。因此,它可以在没有警告的情况下进行修改。
  • @WojtekSurowka 当然。但我确信有一些库使用这个模块,因为它足够稳定。
【解决方案2】:

在调用gen_server:reply(From, Msg) 中,From 不仅仅是客户端:它实际上是一个包含两个值的元组,调用者的进程ID 和唯一引用。我们可以在the implementation of gen_server:reply/2看到这个:

%% -----------------------------------------------------------------
%% Send a reply to the client.
%% -----------------------------------------------------------------
reply({To, Tag}, Reply) ->
    catch To ! {Tag, Reply}.

这个想法是Tag是调用者提供的一个唯一值,以便调用者可以将这个调用的结果与任何其他传入消息区分开来:

Ref = make_ref(),
MyServer ! {'$gen_call', {self(), Ref}, foo},
receive
    {Ref, Reply} -> io:format("Result of foo call: ~p~n", [Reply])
end

在上面的代码中,receive 将阻塞,直到它得到对这个调用的响应。

gen_server:call/2 执行与上述类似的操作,并额外监控服务器以防崩溃,并检查超时。)

未记录的原因是它被视为内部实现细节可能会发生变化,建议用户依赖 gen_server:callgen_server:reply 而不是自己生成和匹配消息。


大多数时候您根本不需要使用gen_server:reply/2:服务器进程接收到一个调用并同步处理它,返回一个reply 元组:

handle_call(foo, _From, State) ->
    %% ignoring 'From' here, because we're replying immediately
    {reply, foo_result, State}.

但有时您希望服务器进程延迟响应调用,例如等待网络输入:

handle_call(foo, From, State) ->
    send_request(foo),
    NewState = State#state{pending_request = From},
    {noreply, NewState}.

handle_info({received_response, Response}, State = #state{pending_request = From}) ->
    gen_server:reply(From, Response),
    NewState = State#state{pending_request = undefined},
    {noreply, NewState}.

在上面的示例中,我们将From 值保存在服务器状态中,当响应以 Erlang 消息的形式传入时,我们将其转发给调用者,调用者将一直阻塞直到收到响应。 (一个更现实的例子是同时处理多个请求,并以某种方式将传入的响应与未完成的请求相匹配。)

【讨论】:

猜你喜欢
  • 2013-04-24
  • 2011-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-07
  • 2019-04-29
  • 1970-01-01
相关资源
最近更新 更多