查看源代码 记录和宏
大型程序通常编写为多个文件的集合,各个部分之间具有明确定义的接口。
将更大的示例划分为多个文件
为了说明这一点,前一节中的消息传递示例被划分为以下五个文件
mess_config.hrl
用于配置数据的头文件
mess_interface.hrl
客户端和消息传递器之间的接口定义
user_interface.erl
用于用户界面的函数
mess_client.erl
用于消息传递器客户端的函数
mess_server.erl
用于消息传递器服务器端的函数
在执行此操作时,shell、客户端和服务器之间的消息传递接口被清理并使用记录进行定义。此外,还引入了宏
%%%----FILE mess_config.hrl----
%%% Configure the location of the server node,
-define(server_node, messenger@super).
%%%----END FILE----
%%%----FILE mess_interface.hrl----
%%% Message interface between client and server and client shell for
%%% messenger program
%%%Messages from Client to server received in server/1 function.
-record(logon,{client_pid, username}).
-record(message,{client_pid, to_name, message}).
%%% {'EXIT', ClientPid, Reason} (client terminated or unreachable.
%%% Messages from Server to Client, received in await_result/0 function
-record(abort_client,{message}).
%%% Messages are: user_exists_at_other_node,
%%% you_are_not_logged_on
-record(server_reply,{message}).
%%% Messages are: logged_on
%%% receiver_not_found
%%% sent (Message has been sent (no guarantee)
%%% Messages from Server to Client received in client/1 function
-record(message_from,{from_name, message}).
%%% Messages from shell to Client received in client/1 function
%%% spawn(mess_client, client, [server_node(), Name])
-record(message_to,{to_name, message}).
%%% logoff
%%%----END FILE----
%%%----FILE user_interface.erl----
%%% User interface to the messenger program
%%% login(Name)
%%% One user at a time can log in from each Erlang node in the
%%% system messenger: and choose a suitable Name. If the Name
%%% is already logged in at another node or if someone else is
%%% already logged in at the same node, login will be rejected
%%% with a suitable error message.
%%% logoff()
%%% Logs off anybody at that node
%%% message(ToName, Message)
%%% sends Message to ToName. Error messages if the user of this
%%% function is not logged on or if ToName is not logged on at
%%% any node.
-module(user_interface).
-export([logon/1, logoff/0, message/2]).
-include("mess_interface.hrl").
-include("mess_config.hrl").
logon(Name) ->
case whereis(mess_client) of
undefined ->
register(mess_client,
spawn(mess_client, client, [?server_node, Name]));
_ -> already_logged_on
end.
logoff() ->
mess_client ! logoff.
message(ToName, Message) ->
case whereis(mess_client) of % Test if the client is running
undefined ->
not_logged_on;
_ -> mess_client ! #message_to{to_name=ToName, message=Message},
ok
end.
%%%----END FILE----
%%%----FILE mess_client.erl----
%%% The client process which runs on each user node
-module(mess_client).
-export([client/2]).
-include("mess_interface.hrl").
client(Server_Node, Name) ->
{messenger, Server_Node} ! #logon{client_pid=self(), username=Name},
await_result(),
client(Server_Node).
client(Server_Node) ->
receive
logoff ->
exit(normal);
#message_to{to_name=ToName, message=Message} ->
{messenger, Server_Node} !
#message{client_pid=self(), to_name=ToName, message=Message},
await_result();
{message_from, FromName, Message} ->
io:format("Message from ~p: ~p~n", [FromName, Message])
end,
client(Server_Node).
%%% wait for a response from the server
await_result() ->
receive
#abort_client{message=Why} ->
io:format("~p~n", [Why]),
exit(normal);
#server_reply{message=What} ->
io:format("~p~n", [What])
after 5000 ->
io:format("No response from server~n", []),
exit(timeout)
end.
%%%----END FILE---
%%%----FILE mess_server.erl----
%%% This is the server process of the messenger service
-module(mess_server).
-export([start_server/0, server/0]).
-include("mess_interface.hrl").
server() ->
process_flag(trap_exit, true),
server([]).
%%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
server(User_List) ->
io:format("User list = ~p~n", [User_List]),
receive
#logon{client_pid=From, username=Name} ->
New_User_List = server_logon(From, Name, User_List),
server(New_User_List);
{'EXIT', From, _} ->
New_User_List = server_logoff(From, User_List),
server(New_User_List);
#message{client_pid=From, to_name=To, message=Message} ->
server_transfer(From, To, Message, User_List),
server(User_List)
end.
%%% Start the server
start_server() ->
register(messenger, spawn(?MODULE, server, [])).
%%% Server adds a new user to the user list
server_logon(From, Name, User_List) ->
%% check if logged on anywhere else
case lists:keymember(Name, 2, User_List) of
true ->
From ! #abort_client{message=user_exists_at_other_node},
User_List;
false ->
From ! #server_reply{message=logged_on},
link(From),
[{From, Name} | User_List] %add user to the list
end.
%%% Server deletes a user from the user list
server_logoff(From, User_List) ->
lists:keydelete(From, 1, User_List).
%%% Server transfers a message between user
server_transfer(From, To, Message, User_List) ->
%% check that the user is logged on and who he is
case lists:keysearch(From, 1, User_List) of
false ->
From ! #abort_client{message=you_are_not_logged_on};
{value, {_, Name}} ->
server_transfer(From, Name, To, Message, User_List)
end.
%%% If the user exists, send the message
server_transfer(From, Name, To, Message, User_List) ->
%% Find the receiver and send the message
case lists:keysearch(To, 2, User_List) of
false ->
From ! #server_reply{message=receiver_not_found};
{value, {ToPid, To}} ->
ToPid ! #message_from{from_name=Name, message=Message},
From ! #server_reply{message=sent}
end.
%%%----END FILE---
头文件
如上所示,某些文件具有 .hrl
扩展名。这些是通过以下方式包含在 .erl
文件中的头文件
-include("File_Name").
例如
-include("mess_interface.hrl").
在上面的例子中,该文件是从与消息传递器示例中所有其他文件相同的目录中获取的。(手册)。
.hrl 文件可以包含任何有效的 Erlang 代码,但最常用于记录和宏定义。
记录
记录定义为
-record(name_of_record,{field_name1, field_name2, field_name3, ......}).
例如
-record(message_to,{to_name, message}).
这等效于
{message_to, To_Name, Message}
通过一个示例可以最好地说明如何创建记录
#message_to{message="hello", to_name=fred)
这将创建
{message_to, fred, "hello"}
请注意,创建记录时不必担心为记录的各个部分赋值的顺序。使用记录的优点是,通过将其定义放在头文件中,您可以方便地定义易于更改的接口。例如,如果想向记录添加一个新字段,只需更改使用新字段的代码,而不必在每次引用该记录的地方更改。如果在创建记录时遗漏了一个字段,它将获得原子 undefined
的值。(手册)
使用记录的模式匹配与创建记录非常相似。例如,在 case
或 receive
内
#message_to{to_name=ToName, message=Message} ->
这与以下内容相同
{message_to, ToName, Message}
宏
添加到消息传递器的另一件事是宏。文件 mess_config.hrl
包含以下定义
%%% Configure the location of the server node,
-define(server_node, messenger@super).
该文件包含在 mess_server.erl
中
-include("mess_config.hrl").
现在,mess_server.erl
中每次出现 ?server_node
的地方都被替换为 messenger@super
。
在生成服务器进程时也会使用宏
spawn(?MODULE, server, [])
这是一个标准宏(即由系统定义,而不是由用户定义)。 ?MODULE
始终被当前模块的名称(即文件开头附近的 -module
定义)替换。有更高级的方法可以使用带有例如参数的宏。
消息传递器示例中的三个 Erlang (.erl
) 文件被单独编译成目标代码文件 (.beam
)。当 Erlang 系统在代码执行期间引用这些文件时,它会将这些文件加载并链接到系统中。在本例中,它们只是放在当前工作目录(即您执行 "cd" 的位置)中。有方法可以将 .beam
文件放在其他目录中。
在消息传递器示例中,没有对发送的消息是什么做出假设。它可以是任何有效的 Erlang 项。