ranch实现游戏服务器

  在 erlang游戏开发tcp 我们建立起了自己的socket tcp 服务器的基本骨架。当时面对并发情况下,多人同一时刻连接服务器的时候,我们的基本骨架 还是难以应付处理。这就使我不得不想对这样的情况如何去处理。怎么处理呢? 预先开多个线程侦听连接,侦听到有连接后重新开线程处理当前连接,然后继续侦听连接。当然要自己处理这样的事情,也花费不到多少的时间。但是要弄成成熟稳定的骨架估计还是要花费一段时间的。本着不重复造轮子的原则。在这里我找到了大名鼎鼎cowboy使用的ranch。 它已经相当完美帮我实现了我前面所说的功能,并且经过大量的考验。下面我们就使用ranch 还实现我们的tcp 游戏服务器。

  ranch的使用模式分为两种:第一种独立运行模式;第二种嵌入模式。 cowboy使用的独立运行模式,程序自带的例子也是独立模式运行。在这里我要做的是在ranch嵌入到我们的游戏服务器中,作为我们游戏的一部分运行,在同一监督树下工作。

  整个实现过程在 erlang游戏开发tcp 基础上改造完成。

  1.在rebar.config 中添加对应的依赖项  

  
{deps, [
      {ranch, ".*", {git, "https://github.com/extend/ranch.git", "master"}}
       ]}.
View Code

  2.game_socket_app.erl修改为

  
-module(game_socket_app).

-behaviour(application).

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

-define(PORT,9933).
-define(LISTEMNUM,5).

%% ===================================================================
%% Application callbacks
%% ===================================================================

start(_StartType, _StartArgs) ->
    %%读取启动端口
    Port = case application:get_env(game_socket, port) of
               {ok, P} -> P;
               undefined -> ?PORT
           end,
    %%侦听线程的个数
    ListenNum = case application:get_env(game_socket,listemnum) of
        {ok,L}->L;
         undefined->?LISTEMNUM
        end, 
    ok = game_socket_store:init(),
    %%启动监督树
    case game_socket_sup:start_link([Port,ListenNum]) of
        {ok, Pid} ->
            {ok, Pid};
        Other ->
            {error, Other}
    end.

stop(_State) ->
    ok.
View Code

  3.修改game_socket_sup.erl监督树  

  
-module(game_socket_sup).

-behaviour(supervisor).
-define(CHILD(I, Type,Parms), {I, {I, start_link,Parms}, permanent, 5000, Type, [I]}).
%% ===================================================================
%% API functions
%% ===================================================================
%% API.
-export([start_link/1]).

%% supervisor.
-export([init/1]).

%% API.
start_link([Port,ListenNum]) ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, [Port,ListenNum]).
%--------------------------------------------------------------------
%% @doc init 
%% @spec 
%% @end
%%--------------------------------------------------------------------

init([Port,ListenNum]) ->
     %%启动ranch监督树
     RanchSpec=?CHILD(ranch_sup,supervisor,[]),    
     ListenerSpec = ranch:child_spec(game_socket_server,ListenNum,ranch_tcp, [{port, Port}], game_socket_server, []),
     Childs=[RanchSpec,ListenerSpec],
    {ok, {{one_for_one, 10, 10}, Childs}}.
View Code

  4改造game_socket_server.erl  

  
-module(game_socket_server).
-behaviour(gen_server).
-behaviour(ranch_protocol).

%% API.
-export([start_link/4]).

%% gen_server.
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

-define(TIMEOUT, 500000).

-define(SERVER, ?MODULE).

-record(state, {ref,socket, transport,otp,ip,port}).

%% API.

start_link(Ref, Socket, Transport, Opts) ->
    gen_server:start_link(?MODULE, [Ref, Socket, Transport, Opts], []).

%% gen_server.

%% This function is never called. We only define it so that
%% we can use the -behaviour(gen_server) attribute.

init([Ref, Socket, Transport,Opts]) ->
    %%peername(Socket) -> {ok, {Address, Port}} | {error, posix()}      
    timer:send_interval(1000,timertick),
    {ok,{Address,Port}} = inet:peername(Socket),
    {ok, {state, Ref, Socket, Transport,Opts,Address,Port}, 0}.
%% timout function set opt parms
handle_info(timeout, State=#state{ref=Ref, socket=Socket, transport=Transport}) ->
    ok = ranch:accept_ack(Ref),
    ok = Transport:setopts(Socket, [{active, once}]),
    game_socket_store:insert(self(),Socket),
    {noreply, State};
%% handle socket data 
handle_info({tcp, Socket, Data}, State=#state{socket=Socket, transport=Transport}) ->
    Transport:setopts(Socket, [{active, once}]),
        io:format("~p~n",[Data]),
        lists:foreach(fun(Pid) ->
                  case Pid =:= self() of
                  false ->
                      gen_server:cast(Pid,{chat,Data});
                  true -> ok
                end           
              end,
              game_socket_store:lookall()),
    {noreply, State, ?TIMEOUT};
handle_info(timertick,State=#state{socket=Socket,transport=Transport})->
    Transport:send(Socket,<<1>>),
    {noreply,State};

handle_info({tcp_closed, _Socket}, State) ->
    {stop, normal, State};
handle_info({tcp_error, _, Reason}, State) ->
    {stop, Reason, State};
handle_info(timeout, State) ->
    {stop, normal, State};
handle_info(_Info, State) ->
    {stop, normal, State}.

handle_call(_Request, _From, State) ->
         io:format("handle_call message ~p ~n",[_Request]),
    {reply, ok, State}.

handle_cast({chat,Msg}, State=#state{socket=Socket, transport=Transport}) ->
    Transport:send(Socket,Msg),
    io:format("handle_cast message ~p ~n",[Msg]),
    {noreply, State}.

terminate(_Reason, _State) ->
    game_socket_store:delete(self()),
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.
View Code

  经过以上4部我们初步的socket服务器搞定。怎么样感觉简单吧

  最后 rebar g-d   ./start-dev.sh  appmon:start().去看看监督树吧。然后在添加几个连接看看监督树。

原文地址:https://www.cnblogs.com/codew/p/3829129.html