Erlang 完善后实例

增加健壮性后的完整示例

让我们改进 Messager 程序以增加该程序的健壮性:

  1. %%% Message passing utility.
  2. %%% User interface:
  3. %%% login(Name)
  4. %%% One user at a time can log in from each Erlang node in the
  5. %%% system messenger: and choose a suitable Name. If the Name
  6. %%% is already logged in at another node or if someone else is
  7. %%% already logged in at the same node, login will be rejected
  8. %%% with a suitable error message.
  9. %%% logoff()
  10. %%% Logs off anybody at that node
  11. %%% message(ToName, Message)
  12. %%% sends Message to ToName. Error messages if the user of this
  13. %%% function is not logged on or if ToName is not logged on at
  14. %%% any node.
  15. %%%
  16. %%% One node in the network of Erlang nodes runs a server which maintains
  17. %%% data about the logged on users. The server is registered as "messenger"
  18. %%% Each node where there is a user logged on runs a client process registered
  19. %%% as "mess_client"
  20. %%%
  21. %%% Protocol between the client processes and the server
  22. %%% ----------------------------------------------------
  23. %%%
  24. %%% To server: {ClientPid, logon, UserName}
  25. %%% Reply {messenger, stop, user_exists_at_other_node} stops the client
  26. %%% Reply {messenger, logged_on} logon was successful
  27. %%%
  28. %%% When the client terminates for some reason
  29. %%% To server: {'EXIT', ClientPid, Reason}
  30. %%%
  31. %%% To server: {ClientPid, message_to, ToName, Message} send a message
  32. %%% Reply: {messenger, stop, you_are_not_logged_on} stops the client
  33. %%% Reply: {messenger, receiver_not_found} no user with this name logged on
  34. %%% Reply: {messenger, sent} Message has been sent (but no guarantee)
  35. %%%
  36. %%% To client: {message_from, Name, Message},
  37. %%%
  38. %%% Protocol between the "commands" and the client
  39. %%% ----------------------------------------------
  40. %%%
  41. %%% Started: messenger:client(Server_Node, Name)
  42. %%% To client: logoff
  43. %%% To client: {message_to, ToName, Message}
  44. %%%
  45. %%% Configuration: change the server_node() function to return the
  46. %%% name of the node where the messenger server runs
  47. -module(messenger).
  48. -export([start_server/0, server/0,
  49. logon/1, logoff/0, message/2, client/2]).
  50. %%% Change the function below to return the name of the node where the
  51. %%% messenger server runs
  52. server_node() ->
  53. messenger@super.
  54. %%% This is the server process for the "messenger"
  55. %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
  56. server() ->
  57. process_flag(trap_exit, true),
  58. server([]).
  59. server(User_List) ->
  60. receive
  61. {From, logon, Name} ->
  62. New_User_List = server_logon(From, Name, User_List),
  63. server(New_User_List);
  64. {'EXIT', From, _} ->
  65. New_User_List = server_logoff(From, User_List),
  66. server(New_User_List);
  67. {From, message_to, To, Message} ->
  68. server_transfer(From, To, Message, User_List),
  69. io:format("list is now: ~p~n", [User_List]),
  70. server(User_List)
  71. end.
  72. %%% Start the server
  73. start_server() ->
  74. register(messenger, spawn(messenger, server, [])).
  75. %%% Server adds a new user to the user list
  76. server_logon(From, Name, User_List) ->
  77. %% check if logged on anywhere else
  78. case lists:keymember(Name, 2, User_List) of
  79. true ->
  80. From ! {messenger, stop, user_exists_at_other_node}, %reject logon
  81. User_List;
  82. false ->
  83. From ! {messenger, logged_on},
  84. link(From),
  85. [{From, Name} | User_List] %add user to the list
  86. end.
  87. %%% Server deletes a user from the user list
  88. server_logoff(From, User_List) ->
  89. lists:keydelete(From, 1, User_List).
  90. %%% Server transfers a message between user
  91. server_transfer(From, To, Message, User_List) ->
  92. %% check that the user is logged on and who he is
  93. case lists:keysearch(From, 1, User_List) of
  94. false ->
  95. From ! {messenger, stop, you_are_not_logged_on};
  96. {value, {_, Name}} ->
  97. server_transfer(From, Name, To, Message, User_List)
  98. end.
  99. %%% If the user exists, send the message
  100. server_transfer(From, Name, To, Message, User_List) ->
  101. %% Find the receiver and send the message
  102. case lists:keysearch(To, 2, User_List) of
  103. false ->
  104. From ! {messenger, receiver_not_found};
  105. {value, {ToPid, To}} ->
  106. ToPid ! {message_from, Name, Message},
  107. From ! {messenger, sent}
  108. end.
  109. %%% User Commands
  110. logon(Name) ->
  111. case whereis(mess_client) of
  112. undefined ->
  113. register(mess_client,
  114. spawn(messenger, client, [server_node(), Name]));
  115. _ -> already_logged_on
  116. end.
  117. logoff() ->
  118. mess_client ! logoff.
  119. message(ToName, Message) ->
  120. case whereis(mess_client) of % Test if the client is running
  121. undefined ->
  122. not_logged_on;
  123. _ -> mess_client ! {message_to, ToName, Message},
  124. ok
  125. end.
  126. %%% The client process which runs on each user node
  127. client(Server_Node, Name) ->
  128. {messenger, Server_Node} ! {self(), logon, Name},
  129. await_result(),
  130. client(Server_Node).
  131. client(Server_Node) ->
  132. receive
  133. logoff ->
  134. exit(normal);
  135. {message_to, ToName, Message} ->
  136. {messenger, Server_Node} ! {self(), message_to, ToName, Message},
  137. await_result();
  138. {message_from, FromName, Message} ->
  139. io:format("Message from ~p: ~p~n", [FromName, Message])
  140. end,
  141. client(Server_Node).
  142. %%% wait for a response from the server
  143. await_result() ->
  144. receive
  145. {messenger, stop, Why} -> % Stop the client
  146. io:format("~p~n", [Why]),
  147. exit(normal);
  148. {messenger, What} -> % Normal response
  149. io:format("~p~n", [What])
  150. after 5000 ->
  151. io:format("No response from server~n", []),
  152. exit(timeout)
  153. end.

主要有如下几处改动:

Messager 服务器捕捉进程退出。如果它收到进程终止信号,{'EXIT',From,Reason},则说明客户端进程已经终止或者由于下面的原因变得不可达:

  • 用户主动退出登录(取消了 “logoff” 消息)。
  • 与客户端连接的网络已经断开。
  • 客户进程所处的结点崩溃。
  • 客户进程执行了某些非法操作。

如果收到上面所述的退出信号,服务器调用 server_logoff 函数将 {From, Name} 元组从 User_Lists 列表中删除。如果服务端所在的结点崩溃了,那么系统将将自动产生进程终止信号,并将其发送给所有的客户端进程:'EXIT',MessengerPID,noconnection},客户端进程收到该消息后会终止自身。

同样,在 await_result 函数中引入了一个 5 秒钟的定时器。也就是说,如果服务器 5 秒钟之类没有回复客户端,则客户端终止执行。这个只是在服务端与客户端建立连接前的登录阶段需要。

一个非常有意思的例子是如果客户端在服务端建立连接前终止会发生什么情况呢?需要特别注意,如果一个进程与另一个不存在的进程建立连接,则会收到一个终止信号 {'EXIT',From, noproc}。这就好像连接建立后进程立马就结束了一样。