- 進程間的消息傳遞是異步的。
- 信箱的大小是沒有上限的。
- 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 服務
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.