我正在使用learnyousomeerlang site慢慢学习erlang语言,而我现在正在使用#34;愤怒对抗有限状态机"章,它构建并描述了trade_fsm.erl的工作原理。作为我学习过程的一部分,我决定为这个系统编写一个界面,你可以通过输入控制台命令来控制两个交易方。我认为我在写作方面做得不错,但由于某些原因我无法理解,每当我尝试开始交易时,客户端都会崩溃。这是怎么回事:
5> z3:init("a", "b").
true
6> z3:display_pids().
First player pid: {<0.64.0>}
Second player pid: {<0.65.0>}.
done
7> z3:p1_propose_trade().
{a}: asking user <0.65.0> for a trade
{b}: <0.64.0> asked for a trade negotiation
done
8> z3:display_pids().
done
9>
这是我的代码:
-module(z3).
-compile(export_all).
-record(state, {player1,
player2,
p1items=[],
p2items=[],
p1state,
p2state,
p1name="Carl",
p2name="FutureJim"}).
init(FirstName, SecondName) ->
{ok, Pid1} = trade_fsm:start_link(FirstName),
{ok, Pid2} = trade_fsm:start_link(SecondName),
S = #state{p1name=FirstName, p2name=SecondName,
player1=Pid1, player2=Pid2,
p1state=idle, p2state=idle},
register(?MODULE, spawn(?MODULE, loop, [S])).
display_pids() ->
?MODULE ! display_pids,
done.
p1_propose_trade() ->
?MODULE ! {wanna_trade, p1},
done.
p2_accept_trade() ->
?MODULE ! {accept_trade, p2},
done.
loop(S=#state{}) ->
receive
display_pids ->
io:format("First player pid: {~p}~nSecond player pid: {~p}.~n", [S#state.player1, S#state.player2]),
loop(S);
{wanna_trade, Player} ->
case Player of
p1 ->
trade_fsm:trade(S#state.player1, S#state.player2);
p2 ->
trade_fsm:trade(S#state.player2, S#state.player1);
_ ->
io:format("[Debug:] Invalid player.~n")
end,
loop(S);
{accept_trade, Player} ->
case Player of
p1 ->
trade_fsm:accept_trade(S#state.player1);
p2 ->
trade_fsm:accept_trade(S#state.player2);
_ ->
io:format("[Debug:] Invalid player.~n")
end,
loop(S);
_ ->
io:format("[Debug:] Received invalid command.~n"),
loop(S)
end.
有谁可以告诉我为什么这段代码失败以及应该如何实现?
答案 0 :(得分:1)
当您致电z3:p1_propose_trade().
时,它会将消息{wanna_trade, p1}
发送到已注册的流程z3。
消息在循环函数中被解释,该函数调用转换为trade_fsm:trade(S#state.player1, S#state.player2);
的{{1}}。此调用是一个同步调用,它等待来自fsm的回复,如果没有收到任何答复,则在30秒后超时。
在状态等待中,您已在语句中捕获到该消息:
gen_fsm:sync_send_event(S#state.player1, {negotiate, S#state.player2}, 30000).
没有回复值返回给调用者。你应该在最后一行使用像
这样的东西idle({negotiate, OtherPid}, From, S=#state{}) ->
ask_negotiate(OtherPid, self()),
notice(S, "asking user ~p for a trade", [OtherPid]),
Ref = monitor(process, OtherPid),
{next_state, idle_wait, S#state{other=OtherPid, monitor=Ref, from=From}};
或显式调用gen_fsm:reply / 2。
我没有在代码中挖掘太多,但是如果你把它改为:
{reply, Reply, idle_wait, S#state{other=OtherPid, monitor=Ref, from=From}};
它不会停止并且似乎正常工作。
也许有些人完全了解gen_fsm的行为可以解释场景背后的内容(为什么在超时结束时没有打印输出,为什么shell准备好了新的命令,而它应该等待一个答案?):
idle({negotiate, OtherPid}, From, S=#state{}) ->
Reply = ask_negotiate(OtherPid, self()),
notice(S, "asking user ~p for a trade", [OtherPid]),
Ref = monitor(process, OtherPid),
{reply, Reply, idle_wait, S#state{other=OtherPid, monitor=Ref, from=From}};
,您将看到它在达到30秒超时之前不会返回,然后您会收到错误消息。trade(OwnPid, OtherPid)
调用它时,30秒后未显示错误消息,但已注册的进程z3死亡。<强> [编辑] 强>
我已经检查了代码应该如何工作,事实上,似乎没有必要修改fsm代码。当第二个用户接受谈判时,回复应该来自进程2。所以你不能这样做测试(循环正在等待答案,它不能发送accept_trade)。这是一个有效的会议:
z3:p1_propose_trade().
您可以更改“wanna_trade”界面以避免阻止问题
{ok,P1} = trade_fsm:start("a1").
{ok,P2} = trade_fsm:start("a2").
T = fun() -> io:format("~p~n",[trade_fsm:trade(P1,P2)]) end.
A = fun() -> io:format("~p~n",[trade_fsm:accept_trade(P2)]) end.
spawn(T). % use another process to avoid the shell to be locked
A().