erlang小demo2_gen_server模擬遊戲伺服器程式管理
題目:在遊戲中,有很多需要使用共享資源來處理的功能,這時候就需要單獨管理程式來分配,並且按照請求有序執行資源分配,假設當前系統有X個資源,每個請求攜帶需要佔用Y個資源,佔用耗時Z秒
題目要求
- 使用gen_server實現該程式管理
- 請求資源訊息,分配資源,資源不足時,返回失敗,耗時結束時釋放資源
- 取消資源訊息,立馬釋放資源
題目解析
首先我們要弄清楚需要實現的需求,有一個共享資源(所有程式都能讀取到)為X數量,然後每個程式向伺服器請求需要Y個資源,並且每個請求耗時Z秒。假如一個程式A向伺服器請求100個資源,且伺服器共享資源有1000個,那伺服器分配成功後就只剩下900個資源,且下一個程式讀取到的是900個資源,注意題目還有一個條件是佔用耗時,比如是程式A請求100個單位的資源耗時10s,意思是10s後伺服器可用的共享資源要加回這100,且第三個要求說程式可以傳送提前取消資源的訊息,大概就是這些。
具體實現(客戶端)
我們先搭建一個gen_server的框架
整體框架
%%%-------------------------------------------------------------------
%%% @author fengshangjiong
%%% @copyright (C) 2020, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 28. 十二月 2020 16:43
%%%-------------------------------------------------------------------
-module(client1).
-author("fengshangjiong").
-behaviour(gen_server).
%% API
-export([start_link/0]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
init([]) ->
{ok, #state{}}.
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(_Request, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
首先複習一下gen_server的結構,有熟悉的start_link初始化的方法,還有同步/非同步接收訊息的handle_call和handle_cast方法,以及沒有返回值的handle_info,終止terminate和code_change程式碼熱換先不說
我們可以給每個申請進來的程式取個名字,比如test1,test2,需要改一個初始化模組
初始化模組
start(Id) ->
gen_server:start(?MODULE, [Id], []).
stop() ->
gen_server:call(?MODULE,stop).
init([Id]) ->
register(list_to_atom("test" ++ integer_to_list(Id)), self()),
{ok, #state{}}.
%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @end
%%--------------------------------------------------------------------
-spec(start_link() ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
start_link(Id) ->
gen_server:start(?MODULE, [Id], []).
不用原有的start_link我們傳入一個Id,給每個客戶端申請一個新的名字
這個時候客戶端的建立流程就完成了,然後是邏輯程式碼的編寫,請求資源訊息和提前取消資源的訊息
業務程式碼
req_resourse(Req_Resources_Amount, Req_time, Name)->
gen_server:call(server1, {apply_resourse, Req_Resources_Amount, Req_time, Name}).
%% 提前取消
req_cancel(Name) ->
Reply = gen_server:call(server1, {req_cancel_early, Name}),
io:format("~p~n", [Reply]).
這裡我給伺服器取名就叫server1,在這裡直接call就行,然後服務端編寫對應的接收程式碼即可
到這裡客戶端程式碼其實已經沒什麼需要新增的,可以在handle_info裡接收一個狀態資訊
handle_info(Msg, State) ->
io:format("~p~n",[Msg]),
{noreply, State}.
然後看看服務端對應接收
具體實現(服務端)
基本框架+初始化
start() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
stop() ->
gen_server:call(?MODULE,stop).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
init([]) ->
{ok, ets:new(?MODULE, [set, named_table])}.
初始化這裡我是建立了跟檔名同名的ets,準備是用來存資源分配資訊的,然後ets的第一個鍵我準備存系統的全域性資源,這樣也好符合共享的條件,所有程式過來都能讀取到這個資源
然後是處理客戶端的請求
處理請求
handle_call({apply_resourse,Req_Resources_Amount, Req_time, Name}, _From, Tab) ->
%%第一次進來,ets裡沒存系統資源有多少,就會存一下,後面進來就有系統資源資料資料了 則不存
case ets:member(Tab,admin) of
false ->
ets:insert(Tab,{admin,1000});
_ ->
ok
end,
%%讀取系統資源
[{_,Resources}] = ets:lookup(Tab, admin),
Reply = case ets:lookup(Tab, Name) of
[] when Resources >= Req_Resources_Amount ->
ets:insert(Tab, {Name, Req_Resources_Amount}),
NewResources = Resources - Req_Resources_Amount,
%% ets:delete(Tab,admin),
ets:insert(Tab, {admin, NewResources}),
erlang:send_after(Req_time, self(), {realse, Name}),
{distribution_success};
[] ->
{resourse_not_enough};
[_] ->
{already_request}
end,
{reply, Reply, Tab};
這裡是第一個請求處理,就是請求資源的處理,包括幾種情況,在ets查詢該程式是否有請求的記錄,如果有就不給請求了,如果沒有,還要看請求的資源是否大於當前系統的剩餘資源,都符合才給請求,每個case最後一句都是返回給終端的列印訊息,類似於日誌,最後按照gen_server中的handle_call返回標準訊息即可
注意其中有一句
erlang:send_after(Req_time, self(), {realse, Name}),
這裡設定了一個定時器,是用來實現請求時間到期後,服務端釋放資源的方法,時間到了服務端給自己傳送一個訊號,給這哥們釋放了不讓他繼續用了,然後下面是釋放資源的處理函式
handle_call({realse,Name}, _From, Tab) ->
Reply = do(Tab, Name),
Name ! {realse_success},
{reply, Reply, Tab};
以及客戶端提前取消資源的函式
handle_call({req_cancel_early, Pid}, _From, Tab) ->
%% io:format("ccccccccc"),
Reply = do(Tab, Pid),
%% io:format("xxxxxxxxxxxx"),
{reply, Reply, Tab};
handle_call(stop, _From, Tab) ->
{stop, normal, stopped, Tab}.
這裡的do是處理訊息釋放的函式,我單獨抽取出來封裝了,因為在第二個handle_call中呼叫了自己,當然不能再handle_call中呼叫handle_call的方法,你不死鎖誰死鎖,要在handle_info裡處理服務端自己傳送釋放的訊息(其實我感覺這裡可以在客戶端做,但是不知道咋實現)
handle_info({realse,Name}, Tab) ->
Reply = do(Tab, Name),
Name ! {realse_success},
{noreply, Tab}.
釋放請求的程式碼
%%%===================================================================
%%% Internal functions
%%%===================================================================
do(Tab, Pid) ->
case ets:lookup(Tab, Pid) of
[_] ->
[{_,Resources}] = ets:lookup(Tab, admin),
[{_,Release_Resources}] = ets:lookup(Tab, Pid),
NewResources = Resources + Release_Resources,
ets:delete(Tab, Pid),
%% ets:delete(Tab,admin),
ets:insert(Tab, {admin, NewResources}),
{realse_success};
[] ->
%%可能被客戶端提前取消,即使定時器未關閉,
%% 等定時器到點後也會走這裡在ets中找不到資料返回
{already_realse}
end.
我們到這裡差不多了,執行下看看,程式碼可能寫的比較累贅,但是邏輯還是很清晰的,如果沒看懂,後面有完整的程式碼,先看執行結果
執行結果
服務端啟動->客戶端啟動->客戶端申請資源->正常結束釋放->客戶端再申請資源->客戶端傳送提前釋放訊號->釋放成功
我們也可以用observer工具觀察ets中的儲存情況
可以明顯發現,ets中有讀取和釋放操作
完整程式碼
client1.erl
%%%-------------------------------------------------------------------
%%% @author fengshangjiong
%%% @copyright (C) 2020, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 29. 十二月 2020 9:02
%%%-------------------------------------------------------------------
-module(client1).
-author("fengshangjiong").
-behaviour(gen_server).
%% API
-export([start_link/0]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-export([
start/1,
start_link/1,
stop/0,
req_resourse/3,
req_cancel/1
]).
-define(SERVER, ?MODULE).
-record(state, {}).
%%題目:在遊戲中,有很多需要使用共享資源來處理的功能,這時候就需要單獨管理程式來分配,
%% 並且按照請求有序執行資源分配,假設當前系統有X個資源,每個請求攜帶需要佔用Y個資源,佔用耗時Z秒
%% 1) 使用gen_server實現該管理程式
%% 2) 請求資源訊息, 分配資源, 資源不足時, 返回失敗, 耗時結束時釋放資源
%% 3) 取消資源訊息, 立馬釋放資源
%%%===================================================================
%%% API
%%%===================================================================
start(Id) ->
gen_server:start(?MODULE, [Id], []).
stop() ->
gen_server:call(?MODULE,stop).
req_resourse(Req_Resources_Amount, Req_time, Name)->
gen_server:call(server1, {apply_resourse, Req_Resources_Amount, Req_time, Name}).
%% 提前取消
req_cancel(Name) ->
{Reply} = gen_server:call(server1, {req_cancel_early, Name}),
io:format("~p~n", [Reply]).
%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @end
%%--------------------------------------------------------------------
-spec(start_link() ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
start_link(Id) ->
gen_server:start(?MODULE, [Id], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% @end
%%--------------------------------------------------------------------
-spec(init(Args :: term()) ->
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} | ignore).
init([Id]) ->
register(list_to_atom("test" ++ integer_to_list(Id)), self()),
{ok, #state{}}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
State :: #state{}) ->
{reply, Reply :: term(), NewState :: #state{}} |
{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_call(_Request, _From, State) ->
{reply, ok, State};
handle_call(stop, _From, State) ->
{stop, normal, stopped, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_cast(Request :: term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_cast(_Request, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_info(Msg, State) ->
io:format("~p~n",[Msg]),
{noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: #state{}) -> term()).
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
Extra :: term()) ->
{ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
server1.erl
%%%-------------------------------------------------------------------
%%% @author fengshangjiong
%%% @copyright (C) 2020, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 29. 十二月 2020 9:04
%%%-------------------------------------------------------------------
-module(server1).
-author("fengshangjiong").
-behaviour(gen_server).
%% API
-export([start_link/0]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-export([
start/0,
stop/0
]).
-define(SERVER, ?MODULE).
-record(state, {}).
%%題目:在遊戲中,有很多需要使用共享資源來處理的功能,這時候就需要單獨管理程式來分配,
%% 並且按照請求有序執行資源分配,假設當前系統有X個資源,每個請求攜帶需要佔用Y個資源,佔用耗時Z秒
%% 1) 使用gen_server實現該管理程式
%% 2) 請求資源訊息, 分配資源, 資源不足時, 返回失敗, 耗時結束時釋放資源
%% 3) 取消資源訊息, 立馬釋放資源
%%%===================================================================
%%% API
%%%===================================================================
start() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
stop() ->
gen_server:call(?MODULE,stop).
%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @end
%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% @end
%%--------------------------------------------------------------------
init([]) ->
{ok, ets:new(?MODULE, [set, named_table])}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @end
%%--------------------------------------------------------------------
handle_call({apply_resourse,Req_Resources_Amount, Req_time, Name}, _From, Tab) ->
%%第一次進來,ets裡沒存系統資源有多少,就會存一下,後面進來就有系統資源資料資料了 則不存
case ets:member(Tab,admin) of
false ->
ets:insert(Tab,{admin,1000});
_ ->
ok
end,
%%讀取系統資源
[{_,Resources}] = ets:lookup(Tab, admin),
Reply = case ets:lookup(Tab, Name) of
[] when Resources >= Req_Resources_Amount ->
ets:insert(Tab, {Name, Req_Resources_Amount}),
NewResources = Resources - Req_Resources_Amount,
%% ets:delete(Tab,admin),
ets:insert(Tab, {admin, NewResources}),
erlang:send_after(Req_time, self(), {realse, Name}),
{distribution_success};
[] ->
{resourse_not_enough};
[_] ->
{already_request}
end,
{reply, Reply, Tab};
handle_call({realse,Name}, _From, Tab) ->
Reply = do(Tab, Name),
Name ! {realse_success},
{reply, Reply, Tab};
handle_call({req_cancel_early, Pid}, _From, Tab) ->
%% io:format("ccccccccc"),
Reply = do(Tab, Pid),
%% io:format("xxxxxxxxxxxx"),
{reply, Reply, Tab};
handle_call(stop, _From, Tab) ->
{stop, normal, stopped, Tab}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @end
%%--------------------------------------------------------------------
handle_cast(_Request, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_info({realse,Name}, Tab) ->
Reply = do(Tab, Name),
Name ! {realse_success},
{noreply, Tab}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
terminate(_Reason, Tab) ->
io:format("im over ~p~n",[_Reason]),
ok.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
Extra :: term()) ->
{ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
do(Tab, Pid) ->
case ets:lookup(Tab, Pid) of
[_] ->
[{_,Resources}] = ets:lookup(Tab, admin),
[{_,Release_Resources}] = ets:lookup(Tab, Pid),
NewResources = Resources + Release_Resources,
ets:delete(Tab, Pid),
%% ets:delete(Tab,admin),
ets:insert(Tab, {admin, NewResources}),
{realse_success};
[] ->
%%可能被客戶端提前取消,即使定時器未關閉,
%% 等定時器到點後也會走這裡在ets中找不到資料返回
{already_realse}
end.
相關文章
- 模擬supervisor的程式管理
- 遊戲模擬——Position based dynamics遊戲
- 乞丐模擬器Hobo: Tough Life Mac都市生存模擬遊戲Mac遊戲
- 深入小程式系列之一:小程式核心原理及模擬
- 安卓小程式模擬定位測試安卓
- 在你的 Python 遊戲中模擬引力Python遊戲
- 她在橙光遊戲裡模擬人生遊戲
- 治癒系田園生活模擬遊戲,《小森生活》製作人訪談遊戲
- 模擬微信小程式頁面Page方法微信小程式
- 模擬經營手遊之王?《Township》遊戲拆解分析遊戲
- “步行模擬”如何改變電子遊戲?遊戲
- 《全面戰爭模擬器》:詼諧幽默的沙盒戰爭模擬遊戲遊戲
- 關於Python小遊戲程式Python遊戲
- 微信小程式--遊戲demo微信小程式遊戲
- 模擬經營類遊戲(Simulation Game)綜述遊戲GAM
- 當天神也玩模擬經營遊戲時……遊戲
- 神格中文版 for Mac(角色模擬策略遊戲)Mac遊戲
- 殖民者The Colonists for Mac(模擬經營遊戲)Mac遊戲
- 模擬人生4 Mac(經營模擬遊戲)全DLC可無限金幣版Mac遊戲
- “聯合對抗火災”最真實的消防模擬遊戲《模擬消防英豪》遊戲
- 遊戲小程式將成為遊戲公司新增長點遊戲
- 2023年,小程式遊戲前景如何?遊戲
- canvas吃豆小遊戲程式碼Canvas遊戲
- 微信小程式:拼圖遊戲微信小程式遊戲
- 最新版缺氧Oxygen Not Included for Mac,科幻模擬遊戲Mac遊戲
- 異星工廠factorio for mac,模擬經營遊戲Mac遊戲
- 在模擬器遊戲裡體驗另類“人生”遊戲
- 現代的讚歌:交通模擬遊戲史話遊戲
- 異星工廠factorio for mac(模擬經營遊戲)Mac遊戲
- 微信小遊戲大盤留存資料:益智、模擬品類次留高於40%遊戲
- 真·開放式遊戲,谷歌造出首個無限人生模擬遊戲Unbounded遊戲谷歌
- 建築師解構遊戲關卡——模擬遊戲地圖的探討遊戲地圖
- 驅動遊戲世界運轉的“心跳”:遊戲迴圈及實時模擬遊戲
- iOS 如何測試微信小遊戲&小程式?iOS遊戲
- Android如何測試微信小遊戲&小程式?Android遊戲
- iOS如何測試微信小遊戲&小程式?iOS遊戲
- 微信小遊戲開發(8)-模組化遊戲開發
- 經營模擬遊戲:模擬人生4 Mac中文版(全DLC可無限金幣)遊戲Mac