博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
erlang游戏开发tcp
阅读量:5019 次
发布时间:2019-06-12

本文共 10755 字,大约阅读时间需要 35 分钟。

  之前在开发游戏的时候我们采用smartfoxserver这个java开发的游戏引擎,这个引擎在开发回合制游戏方面速度还是不错的。但是面对客户日益增长的需求还是有些力不从心。比如集群,比如灾备,热切换,热更新,热降级,面对上10G的流量攻击,隐藏真实的游戏服务地址等等需求 。这些问题虽然通过java编程和某些方法都能解决。但是权衡起来成本和精力都感觉不值。于是我一直在寻找一种能解决替换smartfoxserver的方案。无意之中发现了erlang恰恰能够解决我上述问题。于是便引起了我对erlang的探究。游戏通信框架主要由 通信,协议,后台服务组成。游戏通信框架我们首先要解决的就是通信问题,所以我第一个探究的也就是tcp socket 通信。

  erlang socket 通信的模式

  1. 主动消息获取(非阻塞)优点 接受消息简单  缺点 当客户端消息远远大于服务器端处理消息的时候 服务器崩溃

      2. 被动消息获取(阻塞)   优点 可以控制处理消息的速度防止流量攻击  缺点 接受消息稍微复杂 要代码处理 recv

  3. 混合消息获取  (半阻塞)优点 接受消息方便,可以防止流量攻击 是前两种模式的一个综合 

  经过多种对比和实验了后我选择了 混合消息获取模式。为了对这种模式的探究,我结合上一篇rebar template 做了一个简单消息发送,接收,连接用户发送消息广播的简单服务器端。下面是我实现的过程。

  1. 使用rebar 创建项目 rebar create template=simple_game projectid=game_socket  

   
==> thinkpad (create)Writing game_socket/README.mdWriting game_socket/MakefileWriting game_socket/.gitignoreWriting game_socket/rebar.configWriting game_socket/start-dev.shWriting game_socket/apps/game_socket/src/game_socket.app.srcWriting game_socket/apps/game_socket/src/reloader.erlWriting game_socket/apps/game_socket/src/game_socket.erlWriting game_socket/apps/game_socket/src/game_socket_app.erlWriting game_socket/apps/game_socket/src/game_socket_sup.erlWriting game_socket/apps/game_socket/src/game_socket_server.erlWriting game_socket/deps/README.mdWriting game_socket/rel/files/vm.argsWriting game_socket/rel/reltool.configWriting game_socket/rel/files/app.configWriting game_socket/rel/files/erlWriting game_socket/rel/files/nodetoolWriting game_socket/rel/files/game_socket
View Code

 创建好的目录树   

   
.├── apps│   └── game_socket│       └── src│           ├── game_socket_app.erl│           ├── game_socket.app.src│           ├── game_socket.erl│           ├── game_socket_server.erl│           ├── game_socket_sup.erl│           └── reloader.erl├── deps│   └── README.md├── erl_crash.dump├── Makefile├── README.md├── rebar.config├── rel│   ├── files│   │   ├── app.config│   │   ├── erl│   │   ├── game_socket│   │   ├── nodetool│   │   └── vm.args│   └── reltool.config└── start-dev.sh
View Code

      2. 修改对应已经创建好的项目

  2.1 game_socket.app.src  添加依赖项目,mnesia

  2.2 改造 game_socket_app.erl start 方法为如下 

  
-define(PORT,9933).%% ===================================================================%% Application callbacks%% ===================================================================start(_StartType, _StartArgs) ->    Port = case application:get_env(game_socket, port) of               {ok, P} -> P;               undefined -> ?PORT           end,    ok = game_socket_store:init(),     {ok, LSock} = gen_tcp:listen(Port, [{active, false}]),    case game_socket_sup:start_link(LSock) of        {ok, Pid} ->            {ok,_Child} = game_socket_sup:start_child(),            {ok, Pid};        Other ->            {error, Other}    end.
View Code

在整个程序代码中要注意匹配确认已经启动成功,如果不匹配可能没有启动成功被忽略。折腾了几个小时血的教训。

  2.3 改造 game_socket_sup.erl  这个文件主要注意 start_child 以及监控方式 重启方式 init 注意init 参数 Socket 是提供给 给game_socket_server.erl 初始使用的。

  
-module(game_socket_sup).-behaviour(supervisor).%% API-export([start_link/1,start_child/0]).%% Supervisor callbacks-export([init/1]).%% ===================================================================%% API functions%% ===================================================================start_link(Lsock) ->    supervisor:start_link({local, ?MODULE}, ?MODULE, [Lsock]).start_child()->    supervisor:start_child(?MODULE,[]).    %% ===================================================================%% Supervisor callbacks%% ===================================================================init([Lsock]) ->    Server = {game_socket_server, {game_socket_server, start_link, [Lsock]},              temporary, brutal_kill, worker, [game_socket_server]},    {ok, { {simple_one_for_one, 0, 1}, [Server]} }.
View Code

  2.4 改照 game_socket_server.erl  整个文件 组要关注 init handle_info handle_info terminate 几个方法即可

  
-module(game_socket_server).-behaviour(gen_server).-define(SERVER, ?MODULE).-define(DFALUTPORT,9933).-record(state ,{lsoket,tick=0,socket}).%% ------------------------------------------------------------------%% API Function Exports%% -------------------------------------------------------------------export([start_link/1]).%% ------------------------------------------------------------------%% gen_server Function Exports%% -------------------------------------------------------------------export([init/1, handle_call/3, handle_cast/2, handle_info/2,         terminate/2, code_change/3]).%% ------------------------------------------------------------------%% API Function Definitions%% ------------------------------------------------------------------start_link(Lsock) ->    gen_server:start_link(?MODULE,[Lsock],[]).%% ------------------------------------------------------------------%% gen_server Function Definitions%% ------------------------------------------------------------------init([Socket]) ->    %% time out      inet:setopts(Socket, [{active, once}]),    {ok, #state{lsoket=Socket},0}.handle_call(_Request, _From, State) ->    {reply, ok, State}.handle_cast({chat,Msg},#state{socket=Socket}=State)->    gen_tcp:send(Socket,io_lib:fwrite("~p~n", [Msg])),    {noreply,State};handle_cast(_Msg, State) ->    {noreply, State}.%--------------------------------------------------------------------%% @doc  recive tcp socket data%% @spec %% @end%%--------------------------------------------------------------------handle_info({tcp, Socket, RawData}, State) ->    inet:setopts(Socket, [{active, once}]),    %% gen_tcp:send(Socket, io_lib:fwrite("~p~n", [RawData])),    lists:foreach(fun(Pid)->              case Pid=:=self() of                 false->                   gen_server:cast(Pid,{chat,RawData});                  _ ->ok                  end          end,          game_socket_store:lookall()),    {noreply, State};%--------------------------------------------------------------------%% @doc  accept socket%% @spec %% @end%%--------------------------------------------------------------------handle_info(timeout, #state{lsoket = LSock} = State) ->    {ok, Sock} = gen_tcp:accept(LSock),    game_socket_store:insert(self(),Sock),    timer:send_interval(1000,timertick),    game_socket_sup:start_child(),    {noreply, State#state{socket=Sock}};handle_info(timertick, #state{socket = _Sock} = State) ->    TickCount = State#state.tick,      %% io:format("~p~n",[TickCount]),   %%en_tcp:send(Sock,<
>), {noreply,State#state{tick=TickCount+1}}; %--------------------------------------------------------------------%% @doc soket close%% @spec %% @end%%--------------------------------------------------------------------handle_info({tcp_closed, _Socket}, State) -> {stop, normal, State};handle_info(_Info, State) -> {noreply, State}.terminate(_Reason, _State) -> game_socket_store:delete(self()), ok.code_change(_OldVsn, State, _Extra) -> {ok, State}.%% ------------------------------------------------------------------%% Internal Function Definitions%% ------------------------------------------------------------------
View Code

  2.5 添加 game_socket_store.erl 在整个数据的存放过程中可以选ets 但是我选着了mnesia 是为后续的集群打下基础,在整个读取写入删除过程没有采取事物处理主要有pid 唯一的考虑 和操作速度较快的理由。如果有上下文逻辑处理可以采用事物处理数据。 

 
%%%-------------------------------------------------------------------%%% @author thinkpad <>%%% @copyright (C) 2014, thinkpad%%% @doc%%%%%% @end%%% Created :  4 Jul 2014 by thinkpad <>%%%--------------------------------------------------------------------module(game_socket_store).-record(user_info,{pid,socket}).%% API-export([init/0,insert/2,lookall/0,delete/1]).%%%===================================================================%%% API%%%===================================================================%--------------------------------------------------------------------%% @doc  init mnesia db %% @spec %% @end%%--------------------------------------------------------------------init()->    db_init().%--------------------------------------------------------------------%% @doc insert pid to db%% @spec %% @end%%--------------------------------------------------------------------insert(Pid,Socket)->    mnesia:dirty_write(#user_info{pid=Pid,socket=Socket}).%--------------------------------------------------------------------%% @doc  get all socket pid %% @spec %% @end%%--------------------------------------------------------------------lookall()->    mnesia:dirty_select(user_info,[{#user_info{pid='$1',socket = '$2'},[],['$1']}]).%--------------------------------------------------------------------%% @doc delete socket pid in db %% @spec %% @end%%--------------------------------------------------------------------delete(Pid)->    mnesia:dirty_delete(user_info,Pid).%%--------------------------------------------------------------------%% @doc%% @spec%% @end%%--------------------------------------------------------------------%%%===================================================================%%% Internal functions%%%===================================================================db_init() ->    delete_schema(),    {atomic, ok} = mnesia:create_table(user_info,[{attributes, record_info(fields,user_info)}]),     ok.delete_schema() ->    mnesia:stop(),    mnesia:delete_schema([node()]),    mnesia:start().
View Code

  3. 开发调试项目  ./start-dev.sh 启动调试项目 调试效果如下图

 
(game_socket@127.0.0.1)10> thinkpad@thinkpad:~/game_socket$ ./start-dev.sh ==> game_socket (clean)==> game_socket (compile)Compiled src/game_socket_store.erl==> rel (compile)==> game_socket (compile)Erlang/OTP 17 [erts-6.1] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]Eshell V6.1  (abort with ^G)(game_socket@127.0.0.1)1> =INFO REPORT==== 4-Jul-2014::11:41:21 ===    application: mnesia    exited: stopped    type: temporary(game_socket@127.0.0.1)1>
View Code

  4. 发布镜像打包 make consle 发布测试 到次一个erlang 基本项目就完成 稍加逻辑处理 就可以改为游戏服务器使用了 聊天 消息 发送 都有了

   
thinkpad@thinkpad:~/game_socket$ make consolerebar compile==> game_socket (compile)Compiled src/game_socket_app.erl==> rel (compile)==> game_socket (compile)rebar generate -f==> rel (generate)WARN:  'generate' command does not apply to directory /home/thinkpad/game_socketrel/game_socket/bin/game_socket consoleExec: /home/thinkpad/game_socket/rel/game_socket/erts-6.1/bin/erlexec -boot /home/thinkpad/game_socket/rel/game_socket/releases/0.1.0/game_socket -embedded -config /home/thinkpad/game_socket/rel/game_socket/etc/app.config -args_file /home/thinkpad/game_socket/rel/game_socket/etc/vm.args -- consoleRoot: /home/thinkpad/game_socket/rel/game_socketErlang/OTP 17 [erts-6.1] [source] [64-bit] [smp:4:4] [async-threads:5] [hipe] [kernel-poll:true]=INFO REPORT==== 4-Jul-2014::11:46:24 ===    application: mnesia    exited: stopped    type: permanentEshell V6.1  (abort with ^G)
View Code

  

  

转载于:https://www.cnblogs.com/codew/p/3823763.html

你可能感兴趣的文章
[RxJS] Split an RxJS observable with window
查看>>
[Angular] Observable.catch error handling in Angular
查看>>
[Practical Git] Configure global settings with git config
查看>>
Android应用程序获取ROOT权限的方法
查看>>
python实现无重复字符串的最长子串
查看>>
记一次空格引发的错误
查看>>
git push 远程新分支
查看>>
python中defaultdict的用法
查看>>
jieba分词的原理
查看>>
CSS3新特性应用之用户体验
查看>>
编译成功,运行闪退
查看>>
C++ Copy Elision
查看>>
一款优秀的在线编辑器:JsBin [使用教程]
查看>>
简单介绍几种Java后台开发常用框架组合
查看>>
继续我的代码,分享我的快乐 - WEBUS2.0 资源汇总
查看>>
10. HTML CSS
查看>>
移植 cloud主要工作点
查看>>
SpringMVC + Spring + Mybatis+ Redis +shiro以及MyBatis学习
查看>>
Game Programming Gems TOC (ZZ)
查看>>
Websocket草案10协议
查看>>