Erlang05_ OTP gen_server
总总篇:14
编辑于 2025/5/12 21:30
截稿于: 2025/5/12 22:50
简介
OTP是Erlang的一个应用框架,提供了一套完整的分布式应用开发组件和设计原则,可以将其理解生态,像Java的Spring那样。
gen_server(模块)则是OTP中具体的一个服务器框架,像SpringBoot那样。
原语
- {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
- 不同回调函数返回不同元组 =gen_server:call(Mod,Args) 同步触发 handle_call
ReturnTuple= gen_server:call(Mod,Args)
向模块Mod发送参数Args,匹配参数的handle_call方法将接收参数,返回服务reply元组,调用后等待回调reply元组
- {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的规范:
- 以功能名分模块。
- 模块内编写export函数。
- 实现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基本能满足一个服务器的开发。
浙公网安备 33010602011771号