檢視原始碼 記錄與巨集

較大型的程式通常會寫成多個檔案的集合,這些檔案之間有明確定義的介面。

將大型範例分割成數個檔案

為了說明這一點,前一節的 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 的值。(手冊

與記錄的模式匹配與建立記錄非常相似。例如,在 casereceive

#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 項。