【问题标题】:Erlang: How does one keep track of senders in a messaging system?Erlang:如何跟踪消息系统中的发件人?
【发布时间】:2012-10-04 03:15:18
【问题描述】:

我是 erlang 新手,我正在尝试构建我的第一个消息传递应用程序。我的问题有 2 个部分

当我在终端上打开两个单独的 erlang 控制台而不指定 -sname 属性时,两个 erlang 控制台如何具有相同的进程 pid。它们实际上是相同的过程吗,如何?

Terminal #1 -------------- $> erl Erlang R15B01 (erts-5.9.1) [source] [smp:4:4] [async-threads:0] [hipe] [kernel-poll:false] Eshell V5.9.1 (abort with ^G) 1> self(). 0.31.0

同样适用于 2 号航站楼

Erlang R15B01 (erts-5.9.1) [source] [smp:4:4] [async-threads:0] [hipe] [kernel-poll:false] Eshell V5.9.1 (abort with ^G) 1> self(). 0.31.0

两者如何在同一个进程上运行。这些是 erlang 进程而不是本机进程吗?

如果我正在构建一个消息传递应用程序,并且我需要某种方法来跟踪每个用户发送的消息的信息。我尝试使用的基本设计是为每个用户创建一个新的聊天客户端。我生成了一个进程来跟踪接收消息的人,从而将他的昵称存储在那里。

同样,我创建了另一个模块,用于跟踪谁发送了消息,但在这种情况下,我使用 self() Pid 来跟踪发件人。这发生在chat_client_sender 模块中。

-module(chat_client). -export([start/0, stop/0, loop/1, login/3, logout/1, send_message/2]). -define(SERVER, chat_client). start() -> message_router:start(), chat_client_sender:start(). stop() -> message_router:stop(), chat_client_sender:stop(). login(Uid, Password, Nickname) -> io:format("~p My Pid", [self()]), Pid = spawn(chat_client, loop, [Nickname]), case message_router:login(Uid, Password, Nickname, Pid) of {ok, logged_in} -> chat_client_sender:add_sender(self(), {Uid, Nickname}), {ok, logged_in}; {error, invalid_uid_or_pwd} -> {error, invalid} end. logout(Uid) -> case message_router:logout(Uid) of {ok, logged_out} -> {ok, logged_out}; ignored -> ignored; _Someshit -> io:format("Some Shit ~p", _Someshit) end. send_message(ToUid, MessageBody) -> chat_client_sender:send_message(ToUid, MessageBody). loop(Nickname) -> receive {print_msg, Messagebody, SenderNickname} -> io:format("~p: ~p to ~p~n", [SenderNickname, Messagebody, Nickname]), loop(Nickname); stop -> ok end.

chat_client_sender 模块

-module(chat_client_sender). -export([start/0, stop/0, add_sender/2, loop/1, get_sender/1, send_message/2]). -define(SERVER, chat_client_sender). start() -> erlang:register(?SERVER, spawn(chat_client_sender, loop, [dict:new()])). stop() -> ?SERVER ! stop. add_sender(SenderPid, {Uid, Nickname}) -> io:format("Adding Sender ~p ~p ~p ~n", [SenderPid, Uid, Nickname]), ?SERVER ! {add_sender, SenderPid, {Uid, Nickname}}. get_sender(SenderPid) -> ?SERVER ! {get_sender, SenderPid}. send_message(ToUid, MessageBody) -> ?SERVER ! {send_msg, ToUid, MessageBody}. loop(MemberPids) -> receive {add_sender, SenderPid, {Uid, Nickname}} -> case dict:find(SenderPid, MemberPids) of {ok, {Uid, Nickname}} -> io:format("Pid exists ~n"), loop(MemberPids); error -> loop(dict:store(SenderPid, {Uid, Nickname}, MemberPids)) end; {send_msg, ToUid, MessageBody, SenderPid} -> case get_sender(SenderPid, MemberPids) of {found, _Uid, Nickname} -> message_router:send_message(ToUid, MessageBody, Nickname), loop(MemberPids); not_found -> not_found, loop(MemberPids) end; {remove_sender, SenderPid} -> case get_sender(SenderPid, MemberPids) of {found, _Uid, _Nickname} -> loop(dict:erase(SenderPid, MemberPids)); not_found -> ignored, loop(MemberPids) end; {get_sender, SenderPid} -> case get_sender(SenderPid, MemberPids) of Any -> io:format("GET SENDER ~p~n", [Any]) end, loop(MemberPids); stop -> ok end. get_sender(SenderPid, MemberPids) -> case dict:find(SenderPid, MemberPids) of {ok, {Uid, Nickname}} -> {found, {Uid, Nickname}}; error -> not_found end.

所以我的应用程序在我的loop 方法中的add_sender 子句开始失败,该方法存储来自chat_client 的SenderPid。

这是一个例子

chat_client:start(). true 9> chat_client_sender:add_sender(self(), {'sid@abc.com', 'sid'}). Adding Sender 'sid@abc.com' sid {add_sender,,{'sid@abc.com',sid}} 10> chat_client_sender:add_sender(self(), {'sid1@abc.com', 'sid1'}). Adding Sender 'sid1@abc.com' sid1 {add_sender,,{'sid1@abc.com',sid1}} 11> =ERROR REPORT==== 13-Oct-2012::19:12:42 === Error in process with exit value: {{case_clause,{ok,{'sid@abc.com',sid}}},[{chat_client_sender,loop,1,[{file,"chat_client_sender.erl"},{line,25}]}]}

根据我的理解,它应该简单地继续 chat_client_sender 模块中的 {ok, {Uid, Nickname}} 子句的尾递归。

... receive {add_sender, SenderPid, {Uid, Nickname}} -> case dict:find(SenderPid, MemberPids) of {ok, {Uid, Nickname}} -> io:format("Pid exists ~n"), loop(MemberPids); error -> loop(dict:store(SenderPid, {Uid, Nickname}, MemberPids)) end; ...

如果能帮助我理解这里发生的事情,我将不胜感激。另外,如果您能查看我的代码并告诉我更多关于最佳实践和我可以做得更好的事情,我将非常感激。我的代码可在goo.gl/yY4kR 获得

谢谢

【问题讨论】:

    标签: erlang messaging pid


    【解决方案1】:

    回答您的第一个问题:erlang 进程不是本机进程,而是“内部”进程。因此,当运行两个 erlang 节点 时,每个节点都有自己的一组进程。在这两种情况下,您调用 self() 的 shell 进程都具有相同的 pid(进程标识符)。

    没有隐含或内置的方式来确定谁发送了消息。标准方法是按照您的示例进行操作,并明确让消息包含发件人。我们发现这是最好的方法。

    【讨论】:

      【解决方案2】:

      第一个问题:

      PID的第一部分表示进程在其中运行的erlang节点,0表示该进程正在本地节点中运行。现在,你得到的 PID 是 shell 的 PID,它只是 erlang 进程;例如,它会死亡(并自动重生):

      1> self().
      <0.32.0>
      2> [X] = [4,2].
      ** exception error: no match of right hand side value [4,2]
      3> self().     
      <0.35.0>
      

      第二个问题:

      错误位于此处(顺便说一句,找到有问题的代码的最小部分非常有用,不仅在您自己进行调试时,而且在您发布问题时 - 通常更多人会尝试在代码时提供帮助你需要学习的东西很少。而且,据我所知,SO 并不真正专注于代码审查):

      loop(MemberPids) ->
          receive
              {add_sender, SenderPid, {Uid, Nickname}} ->
                  case dict:find(SenderPid, MemberPids) of
                      {ok, {Uid, Nickname}} ->    
                          io:format("Pid exists ~n"),             
                          loop(MemberPids);
                      error ->
                          loop(dict:store(SenderPid, {Uid, Nickname}, MemberPids))
                  end
          end.
      

      所以,loop/1 接收到一种消息,形式为 {add_sender, SPID, {UID, Nick}}
      当它收到这样的消息时,它会在字典中搜索以值SPID 为键的记录 字典以error{ok, {UID2, Nick2}} 形式的touple 响应;您使用 case 将它们分开。
      但是,当您尝试匹配 {ok, {UID2, Nick2}} 时,您不会使用新变量;您使用旧的、已经实例化的变量{UID, Nick},因此,如果它们不同,它将失败(因为没有大小写匹配的子句)。

      那你能做什么?由于您无论如何都不使用它们,您可以将它们替换为下划线:

      loop(MemberPids) ->
          receive
              {add_sender, SenderPid, {Uid, Nickname}} ->
                  case dict:find(SenderPid, MemberPids) of
                      {ok, {_, _}} ->    
                          io:format("Pid exists ~n"),             
                          loop(MemberPids);
                      error ->
                          loop(dict:store(SenderPid, {Uid, Nickname}, MemberPids))
                  end
          end.
      

      顺便说一句,由于您从不单独使用 UidNickname,您可以将元组 {Uid,Nickname} 替换为变量:

      loop(MemberPids) ->
          receive
              {add_sender, SenderPid, Value} ->
                  case dict:find(SenderPid, MemberPids) of
                      {ok, _} ->    
                          io:format("Pid exists ~n"),             
                          loop(MemberPids);
                      error ->
                          loop(dict:store(SenderPid, Value, MemberPids))
                  end
          end.
      

      好吧,它并不完全等效,因为如果字典返回像 {ok, {a,b,c}} 这样的值,第一个版本将失败,但我真的不明白这个检查的意义。

      此外,最好(至少在语义上)使用dict:is_key/2,因为您只想检查新元素是否已经存在。它也可以更快,具体取决于实现,因为它只需要查看键是否存在而不检索值;但是,我还没有测试过它,所以也许没有这样的优化。此外,对于一个小规模的项目来说,它可能没有任何区别。

      但请注意,有一种特殊情况:如果您尝试添加具有相同 PID 和相同值的人(本质上是重新注册同一个用户),该怎么办。当前的实现仍然给出相同的错误。根据您的规格,您可能需要更改它。

      玩得开心!

      【讨论】:

      • 谢谢。您的回答和投入是无价的。
      猜你喜欢
      • 1970-01-01
      • 2020-01-16
      • 2021-02-28
      • 2012-11-17
      • 1970-01-01
      • 2014-05-07
      • 2022-11-22
      • 1970-01-01
      • 2011-04-20
      相关资源
      最近更新 更多