使用 gen 伺服器行為

gen_server 是一個像伺服器一樣工作的特定有限狀態機。gen_server 可以處理不同型別的事件:

  • handle_call 的同步請求
  • handle_cast 的非同步請求
  • handle_info 的其他訊息(未在 OTP 規範中定義)

同步和非同步訊息在 OTP 中指定,並且是帶有任何型別資料的簡單標記元組。

一個簡單的 gen_server 定義如下:

-module(simple_gen_server).
-behaviour(gen_server).
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

start_link() ->
    Return = gen_server:start_link({local, ?MODULE}, ?MODULE, [], []),
    io:format("start_link: ~p~n", [Return]),
    Return.

init([]) ->
    State = [],
    Return = {ok, State},
    io:format("init: ~p~n", [State]),
    Return.

handle_call(_Request, _From, State) ->
    Reply = ok,
    Return = {reply, Reply, State},
    io:format("handle_call: ~p~n", [Return]),
    Return.

handle_cast(_Msg, State) ->
    Return = {noreply, State},
    io:format("handle_cast: ~p~n", [Return]),
    Return.

handle_info(_Info, State) ->
    Return = {noreply, State},
    io:format("handle_info: ~p~n", [Return]),
    Return.

terminate(_Reason, _State) ->
    Return = ok,
    io:format("terminate: ~p~n", [Return]),
    ok.

code_change(_OldVsn, State, _Extra) ->
    Return = {ok, State},
    io:format("code_change: ~p~n", [Return]),
    Return.

此程式碼很簡單:收到的每條訊息都列印到標準輸出。

gen_server 行為

要定義 gen_server,你需要使用 -behaviour(gen_server) 在原始碼中明確宣告它。注意,behaviour 可以用美國(行為)或英國(行為)來寫。

此函式是呼叫另一個函式的簡單快捷方式:gen_server:start_link/3,4

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

當你想要啟動連結到 supervisor 或其他程序的伺服器時,將呼叫此函式。start_link/3,4 可以自動註冊你的過程(如果你認為你的過程需要是唯一的),或者可以像簡單的過程那樣簡單地生成它。呼叫時,此函式執行 init/1

此函式可以返回以下定義值:

  • {ok,Pid}
  • ignore
  • {error,Error}

INIT / 1

init([]) ->
    State = [],
    {ok, State}.

init/1 是你的伺服器啟動時第一個執行的功能。這個初始化應用程式的所有先決條件並將狀態返回到新建立的程序。

此函式只能返回這些定義的值:

  • {ok,State}
  • {ok,State,Timeout}
  • {ok,State,hibernate}
  • {stop,Reason}
  • ignore

State 變數可以是所有東西,(例如,列表,元組,支柱,地圖,記錄),並且對於生成的程序內的所有函式都可以訪問。

handle_call / 3

handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

gen_server:call/2 執行此回撥。第一個引數是你的訊息(_Request),第二個引數是請求的起源(_From),最後一個引數是你執行的 gen_server 行為的當前狀態(State)。

如果你想回復呼叫者,handle_call/3 需要返回以下資料結構之一:

  • {reply,Reply,NewState}
  • {reply,Reply,NewState,Timeout}
  • {reply,Reply,NewState,hibernate}

如果你不想回復呼叫者,handle_call/3 需要返回以下資料結構之一:

  • {noreply,NewState}
  • {noreply,NewState,Timeout}
  • {noreply,NewState,hibernate}

如果要停止當前 gen_server 的當前執行,handle_call/3 需要返回以下資料結構之一:

  • {stop,Reason,Reply,NewState}
  • {stop,Reason,NewState}

handle_cast / 2

handle_cast(_Msg, State) ->
    {noreply, State}.

gen_server:cast/2 執行此回撥。第一個引數是你的訊息(_Msg),第二個引數是你正在執行的 gen_server 行為的當前狀態。

預設情況下,此函式無法向呼叫者提供資料,因此,你只有兩個選擇,繼續當前執行:

  • {noreply,NewState}
  • {noreply,NewState,Timeout}
  • {noreply,NewState,hibernate}

或者停止當前的 gen_server 流程:

  • {stop,Reason,NewState}

handle_info / 2

handle_info(_Info, State) ->
    {noreply, State}.

當非標準 OTP 訊息來自外部世界時,執行 handle_info/2。這個不能回覆,像 handle_cast/2 只能做 2 個動作,繼續當前執行:

  • {noreply,NewState}
  • {noreply,NewState,Timeout}
  • {noreply,NewState,hibernate}

或者停止當前執行的 gen_server 程序:

  • {stop,Reason,NewState}

終止/ 2

terminate(_Reason, _State) ->
    ok.

發生錯誤或想要關閉 gen_server 程序時呼叫 terminate/2

code_change / 3

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

當你想要升級正在執行的程式碼時,會呼叫 code_change/3 函式。

此函式只能返回這些定義的值:

  • {ok, NewState}
  • {error, Reason}

開始這個過程

你可以編譯程式碼並啟動 simple_gen_server

simple_gen_server:start_link().

如果要將訊息傳送到伺服器,可以使用以下功能:

% will use handle_call as callback and print:
%   handle_call: mymessage
gen_server:call(simple_gen_server, mymessage).

% will use handle_cast as callback and print:
%   handle_cast: mymessage
gen_server:cast(simple_gen_server, mymessage).

% will use handle_info as callback and print:
%   handle_info: mymessage
erlang:send(whereis(simple_gen_server), mymessage).