Erlang Language
поведение gen_server
Поиск…
замечания
gen_server
является важной особенностью Erlang и требует некоторого предпосылки для понимания каждого аспекта этой функции:
Хороший способ узнать больше об этой функции в Erlang - это прямое чтение исходного кода из официального репозитория github . Поведение gen_server
внедряет множество полезной информации и интересной структуры в ее ядре.
gen_server
определен в gen_server.erl
и соответствующая документация может быть найдена в документации stdlib
Erlang . gen_server
- это функция OTP, и дополнительная информация также может быть найдена в OTP Design Principles и User's Guide .
Фрэнк Хеберт дает вам еще одно хорошее введение в gen_server
из его бесплатной онлайн-книги Learn You Some Erlang для отличного блага!
Официальная документация для обратного вызова gen_server
:
Служба Greeter
Ниже приведен пример службы, которая приветствует людей по данному имени и отслеживает, сколько пользователей оно встретило. См. Использование ниже.
%% greeter.erl
%% Greets people and counts number of times it did so.
-module(greeter).
-behaviour(gen_server).
%% Export API Functions
-export([start_link/0, greet/1, get_count/0]).
%% Required gen server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-record(state, {count::integer()}).
%% Public API
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, {}, []).
greet(Name) ->
gen_server:cast(?MODULE, {greet, Name}).
get_count() ->
gen_server:call(?MODULE, {get_count}).
%% Private
init({}) ->
{ok, #state{count=0}}.
handle_cast({greet, Name}, #state{count = Count} = State) ->
io:format("Greetings ~s!~n", [Name]),
{noreply, State#state{count = Count + 1}};
handle_cast(Msg, State) ->
error_logger:warning_msg("Bad message: ~p~n", [Msg]),
{noreply, State}.
handle_call({get_count}, _From, State) ->
{reply, {ok, State#state.count}, State};
handle_call(Request, _From, State) ->
error_logger:warning_msg("Bad message: ~p~n", [Request]),
{reply, {error, unknown_call}, State}.
%% Other gen_server callbacks
handle_info(Info, State) ->
error_logger:warning_msg("Bad message: ~p~n", [Info]),
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
Вот пример использования этой службы в оболочке erlang:
1> c(greeter).
{ok,greeter}
2> greeter:start_link().
{ok,<0.62.0>}
3> greeter:greet("Andriy").
Greetings Andriy!
ok
4> greeter:greet("Mike").
Greetings Mike!
ok
5> greeter:get_count().
{ok,2}
Использование поведения gen_server
gen_server
- это конечная конечная машина, работающая как сервер. gen_server
может обрабатывать различные типы событий:
- синхронный запрос с
handle_call
- асинхронный запрос с
handle_cast
- другое сообщение (не определено в спецификации OTP) с помощью
handle_info
Синхронное и асинхронное сообщение указываются в 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
может быть написано в США (поведение) или Великобритании (поведение).
start_link / 0
Эта функция представляет собой простой ярлык для вызова другой функции gen_server:start_link/3,4
.
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
переменная может быть все, (например , список, кортеж, proplists, карта, запись) и остаются доступными для всех функций внутри порожденного процесса.
handle_call / 3
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
gen_server:call/2
выполнить этот обратный вызов. Первым аргументом является ваше сообщение ( _Request
), второе - начало запроса ( _From
), а последнее - текущее состояние ( State
) вашего поведения gen_server.
Если вы хотите получить ответ от вызывающего, 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}.
handle_info/2
выполняется, когда нестандартное сообщение OTP поступает из внешнего мира. Это не может ответить и, подобно handle_cast/2
может выполнять только 2 действия, продолжая текущее выполнение:
-
{noreply,NewState}
-
{noreply,NewState,Timeout}
-
{noreply,NewState,hibernate}
Или остановите текущий текущий процесс gen_server
:
-
{stop,Reason,NewState}
прекратить / 2
terminate(_Reason, _State) ->
ok.
terminate/2
вызывается при возникновении ошибки или когда вы хотите gen_server
процесс gen_server
.
code_change / 3
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
code_change/3
вызывается, когда вы хотите обновить 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).
Простая база данных ключей / значений
Этот исходный код создает простой ключ / значение магазина обслуживание , основанное на map
Erlang структура данных. Во-первых, нам нужно определить всю информацию, касающуюся нашего gen_server
:
-module(cache).
-behaviour(gen_server).
% our API
-export([start_link/0]).
-export([get/1, put/2, state/0, delete/1, stop/0]).
% our handlers
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
% Defining our function to start `cache` process:
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%%%%%%%%%%%%%%
% API
% Key/Value database is a simple store, value indexed by an unique key.
% This implementation is based on map, this datastructure is like hash
# in Perl or dictionaries in Python.
% put/2
% put a value indexed by a key. We assume the link is stable
% and the data will be written, so, we use an asynchronous call with
% gen_server:cast/2.
put(Key, Value) ->
gen_server:cast(?MODULE, {put, {Key, Value}}).
% get/1
% take one argument, a key and will a return the value indexed
% by this same key. We use a synchronous call with gen_server:call/2.
get(Key) ->
gen_server:call(?MODULE, {get, Key}).
% delete/1
% like `put/1`, we assume the data will be removed. So, we use an
% asynchronous call with gen_server:cast/2.
delete(Key) ->
gen_server:cast(?MODULE, {delete, Key}).
% state/0
% This function will return the current state (here the map who contain all
% indexed values), we need a synchronous call.
state() ->
gen_server:call(?MODULE, {get_state}).
% stop/0
% This function stop cache server process.
stop() ->
gen_server:stop(?MODULE).
%%%%%%%%%%%%%%%
% Handlers
% init/1
% Here init/1 will initialize state with simple empty map datastructure.
init([]) ->
{ok, #{}}.
% handle_call/3
% Now, we need to define our handle. In a cache server we need to get our
% value from a key, this feature need to be synchronous, so, using
% handle_call seems a good choice:
handle_call({get, Key}, From, State) ->
Response = maps:get(Key, State, undefined),
{reply, Response, State};
% We need to check our current state, like get_fea
handle_call({get_state}, From, State) ->
Response = {current_state, State},
{reply, Response, State};
% All other messages will be dropped here.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
% handle_cast/2
% put/2 will execute this function.
handle_cast({put, {Key, Value}}, State) ->
NewState = maps:put(Key, Value, State),
{noreply, NewState};
% delete/1 will execute this function.
handle_cast({delete, Key}, State) ->
NewState = maps:remove(Key, State),
{noreply, NewState};
% All other messages are dropped here.
handle_cast(_Msg, State) ->
{noreply, State}.
%%%%%%%%%%%%%%%%
% other handlers
% We don't need other features, other handlers do nothing.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
Использование нашего кеш-сервера
Теперь мы можем скомпилировать наш код и начать использовать его с erl
.
% compile cache
c(cache).
% starting cache server
cache:start_link().
% get current store
% will return:
% #{}
cache:state().
% put some data
cache:put(1, one).
cache:put(hello, bonjour).
cache:put(list, []).
% get current store
% will return:
% #{1 => one,hello => bonjour,list => []}
cache:state().
% delete a value
cache:delete(1).
cache:state().
% #{1 => one,hello => bonjour,list => []}
% stopping cache server
cache:stop().