Erlang05_ OTP gen_server

总总篇:14

编辑于 2025/5/12 21:30

截稿于: 2025/5/12 22:50

简介

OTP是Erlang的一个应用框架,提供了一套完整的分布式应用开发组件和设计原则,可以将其理解生态,像Java的Spring那样。

gen_server(模块)则是OTP中具体的一个服务器框架,像SpringBoot那样。

原语

  1. {ok, Pid} |{error,Reson} =gen_server:start_link({Node, Name}, Mod, [Args], [Options]) 创建服务
{ok, Pid} |{error,Reson} =gen_server:start_link({Node, ?MODULE}, ?MODULE, [Args], [Options])
在local节点(本地)注册名为?MODULE(本模块名)的gen_server进程
回调模块指定为?MODULE(本模块)
在回调函数init/1,传入参数[Args1]
配置服务的启动项Options
  1. 不同回调函数返回不同元组 =gen_server:call(Mod,Args) 同步触发 handle_call
ReturnTuple= gen_server:call(Mod,Args)
向模块Mod发送参数Args,匹配参数的handle_call方法将接收参数,返回服务reply元组,调用后等待回调reply元组
  1. {noreply,NewState} =gen_server:cast(Mod,Args) 异步触发 handle_cast
{noreply,NewState} =gen_server:cast(Mod,Args)
向模块Mod发送参数Args,匹配参数的handle_cast方法将接收参数,返回服务noreply元组,调用后立即返回ok

gen_server程序

直接按照gen_server的规范:

  1. 以功能名分模块。
  2. 模块内编写export函数。
  3. 实现gen_server要求的6个回调函数(init/1,handle_call/3,handle_cast/3,handle_info/2,terminate/2,code_change/3)。

可以把它看作一个由gen_server:call驱动的一个State状态机,每次call都是为了改变这个State,其他回调函数都是服务于State的,以下例子中State为一个ets表。以学生管理系统为例,将其改造为gen_erver为例:

%=============record=====================
-record(student, {
    id,
    name,
    age,
    grade,
    evaluation
}).
-module(student_gen).

-include("../include/student.hrl").
%=============export=====================
-export([
    start/0,
    stop/0,
    add_student/1,
    delete_student/1,
    update_student/1,
    find_by_id/1
]).
-export([
    init/1,
    handle_call/3,
    handle_cast/2,
    handle_info/2,
    terminate/2,
    code_change/3
]).
%=============export functions=====================
% start_link:
start() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

stop() -> gen_server:call(?MODULE, stop).

add_student(Student) -> gen_server:call(?MODULE, {add_student, Student}).

delete_student(Id) -> gen_server:call(?MODULE, {delete_student, Id}).

update_student(Student) -> gen_server:call(?MODULE, {update_student, Student}).

find_by_id(Id) -> gen_server:call(?MODULE, {find_by_id, Id}).
% 以下为gen_server模块模板函数
% 服务启动后立即调用,返回值{ok,State},ok为固定返回,State为服务状态,理解为维护的数据,总是传递给其他回调函数的State参数
init([]) ->
    StudentTable = ets:new(student_table, [set, named_table, {keypos, #student.id}]),
    InitialStudents = [
        #student{id = 1, name = "张三", age = 15, grade = "一班", evaluation = excellent},
        #student{id = 2, name = "李四", age = 16, grade = "二班", evaluation = great},
        #student{id = 3, name = "王五", age = 15, grade = "一班", evaluation = great},
        #student{id = 4, name = "赵六", age = 16, grade = "二班", evaluation = normal},
        #student{id = 5, name = "孙七", age = 15, grade = "一班", evaluation = normal}
    ],
    ets:insert(StudentTable, InitialStudents),
    {ok, StudentTable}.
% gen核心回调函数,参数{Args,Pid,State},Args为call传递参数,Pid为传参来的进程Pid,State为服务状态
% 返回值:
% {reply, Reply, NewState}           % 同步返回Reply给调用方,更新State
% {noreply, NewState}             	 % 不同步返回给调用方,需调用方主动gen_server:reply/2阻塞获取Reply 更新State
% {stop, Reason, Reply, NewState}    % 返回Reply后立即停止服务器
% {stop, Reason, NewState}           % 停止服务器
handle_call({add_student, Student}, _From, State) ->
    case ets:insert_new(State, Student) of
        true -> {reply, {ok, "add_success"}, State};
        false -> {reply, {error, "id_exist"}, State}
    end;
handle_call({delete_student, Id}, _From, State) ->
    case ets:delete(State, Id) of
        true -> {reply, {ok, "delete_success"}, State};
        false -> {reply, {error, "student_not_exist"}, State}
    end;
handle_call({update_student, Student}, _From, State) ->
    case ets:lookup(State, Student#student.id) of
        [] ->
            {reply, {error, "student_not_exist"}, State};
        [_] ->
            ets:insert(State, Student),
            {reply, {ok, "update_success"}, State}
    end;
handle_call({find_by_id, Id}, _From, State) ->
    case ets:lookup(State, Id) of
        [] -> {reply, {error, "student_not_exist"}, State};
        [Student] -> {reply, {ok, Student}, State}
    end;
handle_call(stop, _From, State) ->
    {stop, normal, ok, State}.

% 异步触发,调用方无法获得返回值,参数{Args,State},Args为cast传递参数,State为服务状态
% 返回值:
% {noreply, NewState}               % 异步无返回,更新状态为NewState
% {noreply, NewState, Timeout}      % 异步无返回,更新状态为NewState,设置超时
% {stop, Reason, NewState}          % 停止服务
handle_cast(_Msg, State) ->
    {noreply, State}.

% 服务自动接收原生消息(exit,timeout等非自主发送的消息),参数{Info,State},Info为系统消息,State为服务状态
% 返回值:
% {noreply, NewState}               % 处理系统消息,更新状态为NewState
% {noreply, NewState, Timeout}      % 更新状态
% {stop, Reason, NewState}          % 停止服务
handle_info(_Info, State) ->
    {noreply, State}.
% 服务停止后出触发,一般用于清理工作
terminate(_Reason, State) ->
    ets:delete(State),
    ok.
% 热代码更新时触发,更新服务状态(重新编译模块后会触发热代码更新)
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

编写测试模块快速测试gen_server服务

-module(student_test).

-include("../include/student.hrl").

-export([
    start/0,
    stop/0,
    tests/0,
    find_by_id/0,
    add_student/0,
    update_student/0,
    delete_student/0
]).

start() -> student_gen:start().

stop() -> student_gen:stop().

tests() ->
    io:format("~ninto start:~n"),
    start(),
    io:format("~ninto find_by_id:~n"),
    find_by_id(),
    io:format("~ninto add_student:~n"),
    add_student(),
    io:format("~ninto update_student:~n"),
    update_student(),
    io:format("~ninto delete_student:~n"),
    delete_student(),
    io:format("~ninto stop:~n"),
    stop().

find_by_id() ->
    case student_gen:find_by_id(1) of
        {ok, Student} ->
            io:format("find_by_id ok ~p: ~p~n", [1, Student]);
        {error, Reason} ->
            io:format("find_by_id error ~p: ~p~n", [1, Reason])
    end,

    case student_gen:find_by_id(100) of
        {ok, Student2} ->
            io:format("find_by_id ok ~p: ~p~n", [100, Student2]);
        {error, Reason2} ->
            io:format("find_by_id error ~p: ~p~n", [100, Reason2])
    end.

add_student() ->
    Student = #student{
        id = 6,
        name = "小明",
        age = 16,
        grade = "三班",
        evaluation = great
    },
    case student_gen:add_student(Student) of
        {ok, Msg} ->
            io:format("add_student ok ~p: ~p~n", [Student, Msg]);
        {error, Reason} ->
            io:format("add_student error ~p: ~p~n", [Student, Reason])
    end,

    Student1 = #student{
        id = 1,
        name = "第二个张三",
        age = 15,
        grade = "一班",
        evaluation = normal
    },
    case student_gen:add_student(Student1) of
        {ok, Msg2} ->
            io:format("add_student error ~p: ~p~n", [Student1, Msg2]);
        {error, Reason2} ->
            io:format("add_student ok ~p: ~p~n", [Student1, Reason2])
    end.

update_student() ->
    UpdatedStudent = #student{
        id = 6,
        name = "小明",
        age = 17,
        grade = "三班",
        evaluation = excellent
    },
    case student_gen:update_student(UpdatedStudent) of
        {ok, Msg} ->
            io:format("update_student ok ~p: ~p~n", [UpdatedStudent, Msg]);
        {error, Reason} ->
            io:format("update_student error ~p: ~p~n", [UpdatedStudent, Reason])
    end,

    NonExistStudent = #student{
        id = 100,
        name = "not exists",
        age = 15,
        grade = "四班",
        evaluation = normal
    },
    case student_gen:update_student(NonExistStudent) of
        {ok, Msg2} ->
            io:format("update_student error ~p: ~p~n", [NonExistStudent, Msg2]);
        {error, Reason2} ->
            io:format("update_student ok ~p: ~p~n", [NonExistStudent, Reason2])
    end.

delete_student() ->
    case student_gen:delete_student(6) of
        {ok, Msg} ->
            io:format("delete_student ok ~p: ~p~n", [6, Msg]);
        {error, Reason} ->
            io:format("delete_student error ~p: ~p~n", [6, Reason])
    end,

    case student_gen:delete_student(100) of
        {ok, Msg2} ->
            io:format("delete_student error ~p: ~p~n", [100, Msg2]);
        {error, Reason2} ->
            io:format("delete_student ok ~p: ~p~n", [100, Reason2])
    end,

    case student_gen:find_by_id(6) of
        {ok, Student} ->
            io:format("find_by_id ok ~p: ~p~n", [6, Student]);
        {error, Reason3} ->
            io:format("find_by_id error ~p: ~p~n", [6, Reason3])
    end.

一次测试所有功能:

以上API基本能满足一个服务器的开发。

posted on 2025-05-12 22:48  依只  阅读(28)  评论(0)    收藏  举报

导航