檢視原始碼 記錄與巨集
較大型的程式通常會寫成多個檔案的集合,這些檔案之間有明確定義的介面。
將大型範例分割成數個檔案
為了說明這一點,前一節的 messenger 範例被分割成以下五個檔案
mess_config.hrl
設定資料的標頭檔
mess_interface.hrl
用戶端與 messenger 之間的介面定義
user_interface.erl
使用者介面的函式
mess_client.erl
messenger 用戶端的函式
mess_server.erl
messenger 伺服器端的函式
在進行此操作時,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").
在上面的例子中,檔案是從與 messenger 範例中所有其他檔案相同的目錄中取得的。(手冊)。
.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}
巨集
messenger 中新增的另一件事是巨集。檔案 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
定義)。還有更多使用巨集的進階方法,例如使用參數。
messenger 範例中的三個 Erlang (.erl
) 檔案會個別編譯成物件程式碼檔案 (.beam
)。當 Erlang 系統在執行程式碼期間引用這些檔案時,會將它們載入並連結到系統中。在本例中,它們只是放在我們目前的工作目錄中(也就是說,您已執行 "cd" 的地方)。還有其他方式將 .beam
檔案放在其他目錄中。
在 messenger 範例中,沒有對傳送的消息內容做出任何假設。它可以是任何有效的 Erlang 項。