一起做网游吧【5】:服务器架构

生活还要继续,代码还是要不断的写。这次的代码是为未来写的,因为我现在在windows下,而基础的结构代码大部分都写了好,现在在整理,同时进行这个教程。整理好的代码都在linux下,在windows下却又没办法访问。虚拟机还在编译我定制的系统。这些都在之前提到过,因此,先将代码整理出来,回头切换系统的时候再添加进去,然后接着写新的。
这个系统的启动结构,没记错的话,应该是从mochiweb学来的,《Erlang程序设计》也提到过。首先定义一个app:

       {application, netgame,
 [{description, "netgame"},
  {vsn, "0.0.1"},
  {modules, [
    netgame,
    netgame_app,
    netgame_sup,
    netgame_deps
  ]},
  {registered, []},
  {mod, {netgame_app, []}},
  {env, []},
  {applications, [kernel, stdlib, crypto]}]}.
       

上面每个描述的含义在《Erlang程序设计》中均有提到。这里不说了。
一个erlang的application的启动,很标准的:

%% @author --==RIX==-- 
%% @copyright GPL2.0 --==RIX==--.

%% @doc TEMPLATE.

-module(netgame).
-author('--==RIX==-- ').
-export([start/0, stop/0]).

ensure_started(App) ->
    case application:start(App) of
        ok ->
            ok;
        {error, {already_started, App}} ->
            ok
    end.
        
%% @spec start() -> ok
%% @doc Start the netgame server.
start() ->
    netgame_deps:ensure(),
    ensure_started(crypto),
    application:start(netgame).

%% @spec stop() -> ok
%% @doc Stop the netgame server.
stop() ->
    Res = application:stop(netgame),
    application:stop(crypto),
    Res.

       

PS:我故意隐去了我的邮箱,只是为了防止垃圾邮件。

       %% @author --==RIX==-- 
%% @copyright GPL2.0 --==RIX==--.

%% @doc Ensure that the relatively-installed dependencies are on the code
%%      loading path, and locate resources relative
%%      to this application's path.

-module(netgame_deps).
-author('--==RIX==-- ').

-export([ensure/0, ensure/1]).
-export([get_base_dir/0, get_base_dir/1]).
-export([local_path/1, local_path/2]).
-export([deps_on_path/0, new_siblings/1]).

%% @spec deps_on_path() -> [ProjNameAndVers]
%% @doc List of project dependencies on the path.
deps_on_path() ->
    F = fun (X, Acc) ->
                ProjDir = filename:dirname(X),
                case {filename:basename(X),
                      filename:basename(filename:dirname(ProjDir))} of
                    {"ebin", "deps"} ->
                        [filename:basename(ProjDir) | Acc];
                    _ ->
                        Acc
                end
        end,
    ordsets:from_list(lists:foldl(F, [], code:get_path())).
    
%% @spec new_siblings(Module) -> [Dir]
%% @doc Find new siblings paths relative to Module that aren't already on the
%%      code path.
new_siblings(Module) ->
    Existing = deps_on_path(),
    SiblingEbin = filelib:wildcard(local_path(["deps", "*", "ebin"], Module)),
    Siblings = [filename:dirname(X) || X <- SiblingEbin,
                           ordsets:is_element(
                             filename:basename(filename:dirname(X)),
                             Existing) =:= false],
    lists:filter(fun filelib:is_dir/1, 
                 lists:append([[filename:join([X, "ebin"]),
                                filename:join([X, "include"])] ||
                                  X <- Siblings])).
        

%% @spec ensure(Module) -> ok
%% @doc Ensure that all ebin and include paths for dependencies
%%      of the application for Module are on the code path.
ensure(Module) ->
    code:add_paths(new_siblings(Module)),
    code:clash(),
    ok.

%% @spec ensure() -> ok
%% @doc Ensure that the ebin and include paths for dependencies of
%%      this application are on the code path. Equivalent to
%%      ensure(?Module).
ensure() ->
    ensure(?MODULE).

%% @spec get_base_dir(Module) -> string()
%% @doc Return the application directory for Module. It assumes Module is in
%%      a standard OTP layout application in the ebin or src directory.
get_base_dir(Module) ->
    {file, Here} = code:is_loaded(Module),
    filename:dirname(filename:dirname(Here)).

%% @spec get_base_dir() -> string()
%% @doc Return the application directory for this application. Equivalent to
%%      get_base_dir(?MODULE).
get_base_dir() ->
    get_base_dir(?MODULE).

%% @spec local_path([string()], Module) -> string()
%% @doc Return an application-relative directory from Module's application.
local_path(Components, Module) ->
    filename:join([get_base_dir(Module) | Components]).

%% @spec local_path(Components) -> string()
%% @doc Return an application-relative directory for this application.
%%      Equivalent to local_path(Components, ?MODULE).
local_path(Components) ->
    local_path(Components, ?MODULE).

接下来是netgame_app:

%%%-------------------------------------------------------------------
%%% File    : netgame_app.erl
%%% Author  : --==RIX==-- 
%%% Description : 
%%%
%%% Created :  1 Jan 2010 by --==RIX==-- 
%%%-------------------------------------------------------------------
-module(netgame_app).

-behaviour(application).

%% Application callbacks
-export([start/2, stop/1]).

%%====================================================================
%% Application callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start(Type, StartArgs) -> {ok, Pid} |
%%                                     {ok, Pid, State} |
%%                                     {error, Reason}
%% Description: This function is called whenever an application 
%% is started using application:start/1,2, and should start the processes
%% of the application. If the application is structured according to the
%% OTP design principles as a supervision tree, this means starting the
%% top supervisor of the tree.
%%--------------------------------------------------------------------
start(_Type, StartArgs) ->
    netgame_deps:ensure(),
    netgame_sup:start_link().

%%--------------------------------------------------------------------
%% Function: stop(State) -> void()
%% Description: This function is called whenever an application
%% has stopped. It is intended to be the opposite of Module:start/2 and
%% should do any necessary cleaning up. The return value is ignored. 
%%--------------------------------------------------------------------
stop(_State) ->
    ok.

%%====================================================================
%% Internal functions
%%====================================================================

最后一个文件,负责最终启动的supervisor:

%%%-------------------------------------------------------------------
%%% File    : netgame_sup.erl
%%% Author  : --==RIX==-- 
%%% Description : 
%%%
%%% Created :  1 Jan 2010 by --==RIX==-- 
%%%-------------------------------------------------------------------
-module(netgame_sup).

-behaviour(supervisor).

%% API
-export([start_link/0, upgrade/0]).

%% Supervisor callbacks
-export([init/1]).

-define(SERVER, ?MODULE).

%%====================================================================
%% API functions
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the supervisor
%%--------------------------------------------------------------------
start_link() ->
    supervisor:start_link({local, ?SERVER}, ?MODULE, []).

upgrade() ->
    {ok, {_, Specs}} = init([]),

    Old = sets:from_list(
            [Name || {Name, _, _, _} <- supervisor:which_children(?MODULE)]),
    New = sets:from_list([Name || {Name, _, _, _, _, _} <- Specs]),
    Kill = sets:subtract(Old, New),

    sets:fold(fun (Id, ok) ->
                      supervisor:terminate_child(?MODULE, Id),
                      supervisor:delete_child(?MODULE, Id),
                      ok
              end, ok, Kill),

    [supervisor:start_child(?MODULE, Spec) || Spec <- Specs],
    ok.
%%====================================================================
%% Supervisor callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Func: init(Args) -> {ok,  {SupFlags,  [ChildSpec]}} |
%%                     ignore                          |
%%                     {error, Reason}
%% Description: Whenever a supervisor is started using 
%% supervisor:start_link/[2,3], this function is called by the new process 
%% to find out about restart strategy, maximum restart frequency and child 
%% specifications.
%%--------------------------------------------------------------------
init([]) ->
    AChild = [{router,{router,start_link,[]},
              permanent,2000,worker,[router]},
              {server, {server, start,[]},
               permanent, 2000, worker, [server]}
             ],
    {ok,{{one_for_one,3,10}, AChild}}.

%%====================================================================
%% Internal functions
%%====================================================================

上述代码没有太多好说的,如果使用emacs的话,95%的代码都是自动生成的,对netgame_sup.erl文件中的init函数,总共启动了两个节点:router和server。
server的功能就是处理游戏中的一切逻辑,router是负责处理玩家连接的,以及消息的散发,比如向游戏中的全部玩家或者指定的玩家,当玩家登陆进入游戏中的时候,router会生成关于玩家ID和进程的对应表,当玩家退出的时候,就会销毁相对应的记录。在这儿启动是因为这部分代码在很大程度上都是不更改的,而且,对于gen_server的方式,如果要想直接调用route:XXX的话,最好放在supervisor中,当然,这不是绝对的,只是建议,我也不想每次都要手动的输入:

       1>router:start().
       

因此,这里就让它直接启动了。
上述文件均存放于server/src目录中。
PS:slitaz在定制的时候,发现一个问题,在定制的包中(base软件包-busybox+bash+erlang+libunixODBC),如果包含了erlang,则在virtualbox中启动的时候,直接挂掉,虽然会能看到部分的函数栈,但我从没有研究过linux内核的调试,对这方面不清楚,但是,使用

       tazlito emu-iso
       

命令,调用slitaz中的qemu模拟器,却没有问题,而在vmware的虚拟机中,也没有问题,因此,出现了比较搞笑的一幕:
在virtualbox中定制,在vmware中运行。我电脑中的qemu模拟器windows版或许是因为版本太低的问题,直接会挂掉。

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据