2

我是一个 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.


因此,我的应用程序开始add_sender在我的loop方法中的子句中失败,该子句存储来自chat_client.

这是一个例子


 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}]}]} 

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


...
    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 获得

谢谢

4

2 回答 2

6

第一个问题:

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}}
当它接收到这样的消息时,它会在字典中搜索一条记录,该记录具有作为键SPIDerror{ok, {UID2, Nick2}}。你case用来分开那些。
但是,当您尝试匹配{ok, {UID2, Nick2}}时,不要使用新变量;您使用旧的,已经实例化的变量{UID, Nick},因此,如果它们不同,它将失败(因为没有 case 匹配的子句)。

所以,你可以做什么?由于您无论如何都不使用它们,因此您可以用下划线替换它们:

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 和相同值的人(本质上是重新注册同一用户),该怎么办。当前的实现仍然给出相同的错误。根据您的规格,您可能想要更改它。

玩得开心!

于 2012-10-13T17:27:46.577 回答
4

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

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

于 2012-10-13T18:38:19.603 回答