- 进程间的消息传递是异步的。
- 信箱的大小是没有上限的。
- gen_server:call/2 的默认应答等待超时为 5 秒。
- gen_server:
-module(tcp_rpc_server). -behaviour(gen_server). %% API -export([ start_link/1, start_link/0, get_count/0, stop/0 ]). -export([ init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3 ]). -define(SERVER, ?MODULE). -define(DEFAULT_PORT, 1055). -record(state, {port, lsock, request_count = 0}). start_link() -> start_link(?DEFAULT_PORT). start_link(Port) -> gen_server:start_link({local, ?SERVER}, ?MODULE, [Port], []). get_count() -> gen_server:call(?SERVER, get_count). stop() -> gen_server:call(?SERVER, stop). init([Port]) -> {ok, LSock} = gen_tcp:listen(Port, [{active, true}]), {ok, #state{port = Port, lsock = LSock}, 0}. %% 超时值:将超时值置为0就是让gen_server容器在init/1结束后立即触发一次超时,从而迫使进程在完成初始化之后第一时间处理超时消息。 handle_call(get_count, _From, State) -> {reply, {ok, State#state.request_count}, State}. handle_cast(stop, State) -> {stop, normal, State}. handle_info({tcp, Socket, RawData}, State) -> do_rpc(Socket, RawData), RequestCount = State#state.request_count, {noreply, State#state{request_count = RequestCount + 1}}; %% 一种延迟的初始化操作 handle_info(timeout, #state{lsock = LSock} = State) -> {ok, _Sock} = gen_tcp:accept(LSock), {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. do_rpc(Socket, RawData) -> try {M, F, A} = split_out_mfa(RawData), Result = apply(M, F, A), gen_tcp:send(Socket, io_lib:fwrite("~p~n", [Result])) catch _Class:Err -> gen_tcp:send(Socket, io_lib:fwrite("~p~n", [Err])) end. % I/O列表 % io_lib:fwrite/2 的结果不一定是普通字符串(即扁平字符列表)。 % 即便如此,仍然可以直接将结果传给套接字,这个结果被称为 I/O列表:它是一个可以深层嵌套的列表, % 既可以包含字符编码也可以包含二进制数据块。 % 通过这种方式,在依次输出多个I/O列表时,就不用再为了拼接所有数据而专门创建一个中间列表了。 split_out_mfa(RawData) -> MFA = re:replace(RawData, "\r\n$", "", [{return, list}]), {match, [M, F, A]} = re:run(MFA, "(.*):(.*)\s*\\((.*)\s\\)\s*.\s*.\s*$", [{capture, [1, 2, 3], list}, ungreedy]), {list_to_atom(M), list_to_atom(F), args_to_terms(A)}. args_to_terms(RawArgs) -> {ok, Toks, _Line} = erl_scan:string("[" ++ RawArgs ++ "]. ", 1), {ok, Args} = erl_parse:parse_term(Toks), Args. % 带外消息:当服务器需要与第三方模块通信,而第三方模块又依赖于直接消息通信而非OTP库调用时,需要用handle_info % gen_server超时事件 % gen_server设置了超时之后,一旦触发超时,就会产生一条由原子timeout构成的带外消息,这条消息由handle_info/2回调处理, % 该机制常用于处理服务器在超时时间内未收到任何请求的情况,此时可以用它来唤醒服务器并执行一些指定操作。
- 测试框架
- EUnit:
- 主要用于单元测试
- 使用方法:
-include_lib("eunit/include/eunit.hrl"). start_test() -> {ok, _} = tcp_rpc_server:start_link().
ps:该函数必须没有任何参数且函数名要以 _test 结尾。
- 测试命令:
- eunit:test(tcp_rpc_server).
- tcp_rpc_server:test().
- Common Test
基于所谓的 OTP Test Server
- EUnit:
《Erlang/OTP并发编程实战》第三章 开发基于 TCP 的 RPC 服务
猜你喜欢
转载自blog.csdn.net/sanmao123456_/article/details/103419388
今日推荐
周排行