Erlang:如何在邮件系统中跟踪发件人?

时间:2012-10-13 13:51:52

标签: erlang messaging pid

我是一个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到达的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}]}]} 

根据我的理解,它应该继续在{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

谢谢

2 个答案:

答案 0 :(得分: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}}形式的一种消息 当它收到这样的消息时,它会在字典上搜索一个具有值为SPID的键的记录 字典以error或形式为{ok, {UID2, Nick2}}的形式回复;您使用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,你只需用变量替换tu {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和相同值的人(基本上重新注册同一用户),该怎么办?当前的实现仍然给出相同的错误。根据您的规格,您可能希望更改它。

玩得开心!

答案 1 :(得分:4)

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

没有隐含或内置的方法来确定谁发送了消息。标准方法是在您的示例中执行,并明确地让邮件包含发件人。我们发现这是最好的方法。