服务器启动步骤和遇到的一些坑
相关准备
- git
- python
- svn
- erl9.3
- Unity 2018.2.1f1
编译服务器代码
执行步骤
-
拉取SVN中的后端代码
-
代码中每个服务器模块均有一个rebar文件,在git中使用
./rebar+命令
来执行 -
使用rebar的compile命令服务器代码,从Hub中心服务器开始编译
-
以Hub中心服务器运行为例,使用Git执行
sh hub.sh gencmd
命令,会生成start.sh
,debug.sh
,remsh.sh
多个脚本文件 -
执行
sh start.sh
命令,启动服务器
插件安装,编译时报错
- Python插件安装,参考工程下rebar.config文件,进行编译时提示缺啥就补啥
配置文件
-
配置文件在调用时直接使用
ConfigName:get(ConfigID)
的方式,这里存在一个转换,方法大致是从头文件中读取CONFIG_LIST
,再将各配置表逐个转化成通过get
方法返回的map
。参考源代码config.hrl
和common_config.erl
-
当个人新建或修改
*.config
时,应当在config.hrl
中修改自己的*.config
文件路径,然后在服务器Shell中调用lc()
或者common_config:load()
相关方法来更新配置 -
如果使用IDEA,生成太多
erl
配置文件会导致IDEA第一次indexing
等待很久很久很久~可以将文件夹属性设置为exclude
来避免加载
rebar拉取依赖问题
- 使用
./rebar get-deps
拉取依赖时,会提示权限,把Git生成的公钥发给leader
服务器启动问题
-
服务器启动顺序
1.1 优先启动Hub服务器
1.2 服务器启动后调用ss()
命令完成初始化配置 -
启动失败的问题
2.1 命令observer:start().
,用来查看相关的Application是否正常启动
2.2 查看错误日志..\log\error
游戏中的进程与进程字典
使用工具oberver,通过observer:start()
启动,在Process中能够看到所有启动的进程,双击需要查看的进程能够查询到详细的进程信息
一些插件的注意事项
- BSON不支持
[{A, B} | ...]
这种TupleList结构的数据压缩,如果在存Mongo数据库的时候使用了这种类型的数据结构,可以考虑更改成MapList结构[#{A => 1, B => 2} | ...]
- Protocol Buffer,用于消息的压缩,提高传递效率。关于Protocol Buffer,可以参考这篇文章《快来看看Google出品的Protocol Buffer,别只会用Json和XML了》
服务器的通信
-
gen_server提供的是client和server之间,请求和回调的处理
Client --请求-->Server
Server --回应-->Client
-
通信请求的命名:在工程函数
game_proto_route:route(MsgTag)
中,通过解析通信请求得到请求函数的执行模块,所以需要规范请求的命名方式
example:
服务器收到发送玩家公共信息的请求#{'__meta__' => cs_role_public_info,role_id => 200000221}
,这里的MsgTag为cs_role_public_info,我们通过执行route函数,能够得到这个请求位于role_role
模块 -
客户端与服务器通信的过程(以Game服务器为例)
通过查看Game服务器的日志,我们能够看到请求的消息最先到达game_gateway
模块,模块里使用了-behaviour(gen_server)
行为用来处理消息。通过该通用服务器行为,我们只需要去手动定义个人需求的特殊处理,不需要再去关注服务器or进程间的通信细节
为了使数据传输更加高效,在数据的传输时使用Protocol Buffer对数据进行了压缩处理,为了数据格式能够正确的解码,需要格外注意个人编写模块的命名 -
服务器端内部消息的转发处理(以Game服务器为例)
当我们在game_gateway
模块中解析得到了客户端传来的数据后,通过进程调用(game.hrl
中定义好了调用接口)将消息转发到对应的进程模块,相应模块的Server接收到消息后,再具体的执行调用方法 -
异步接收时间限制为40s,如果客户端未在时间限制内发送任何请求(比如心跳包),服务器会返回tcp_error错误,Reson为time_out,即等待超时,服务器会关闭与客户端的通信连接。这样能够避免恶意建立大量socket操作导致的普通请求通信无法建立的情况
例如game_gateway:
prim_inet:async_recv(Socket, ?PACKET_SIZE_BYTES, ?ASYNCRECV_TIMOUT)
user_default文件
该文件中的命令能够在Erlang Shell中直接执行
测试相关
当我们做好一个功能,需要客户端端发送请求来执行时,由于进度原因客户端可能尚未实现该功能,我们可以根据user_default
中的emu
命令来模拟客户端发送消息来进行开发功能的测试。
具体请求格式为:emu(RoleID, #{'__meta__' => A, Data => B, ...})
其中A
为具体的请求名称,Data
和B
分别为请求中附带的类型和参数
Tips
- 当存在大量数据并发操作时,可以考虑对各个进程序列增加随机时间进行差分,避免server在同一时刻处理大量的请求导致的高占用。例如,在开服时玩家大量上线,数据库会定时做持久化操作,如果同时上线人数太多,会导致大量同时进行的数据库写入操作,造成数据库读写的响应延迟,在定时持久化中加入随机时间,可以避免大量请求同时到达,起到将请求分散开的作用
- 写入mongo里面的KV对的V,最好不要用atom原子类型,原子在写入mongo时用的symbol表示,如果直接用mongo的终端更改这些atom类型,atom会变成string,再读出来就变成binary了。这样会导致不匹配,因此会出现用mongo改过的数据再登录就登录不上的问题,最好使用宏定义的方式表示。即用数字序列代替atom原子进行存储
- cowboy转发http请求,以登录为例。
config
中配置data_dispatch
读取到不同的请求后缀,转发到不同的功能handler模块,在各个模块的handle中进行数据校验,校验完成之后的数据,通过cowboy的cowboy_req:reply
方法返回给客户端 - Ranch,Game服中Ranch的启动需要单独开启子进程,因为在ranch中
ranch:accept_ack(Ref)
会阻塞等待shoot
请求成功,参考使用erlang ranch tcp开发服务端
运行ranch:accept_ack/1时,进程会阻塞,等待{shoot, …}这条消息,直到接收到此消息才会继续执行,接着才会完成init。但是{shoot, …}这条消息从哪里来?查下ranch源码不难发现,ranch在建立了与新的gen_server进程的连接后,会向gen_server进程发送该消息(参考ranch_conns_sup:loop/4). 显然,gen_server进程在等待ranch:accept_ack接收到{shoot,…}消息迟迟不能返回,而ranch又无法与gen_server进程连接发送不了{shoot, …}消息,造成死锁。故使用proc_lib:start_link/3优雅地解决了此问题。
- 当个人切换ip之后,除了修改hub_client那里的hub节点ip之外,还有一个地方在login服务器的data_server.config配置表里面
编译客户端代码
执行步骤
- Unity版本=>
2018.2.1f1(64-bit)
- 导入项目,运行客户端
2.1SK框架
→美术工具
→资源导出
如果资源导出时提示Could not find a path of the path 'E:\RE_Client_Code\Assets\SaveLua'
错误,执行步骤2.2
2.2SK框架
→美术工具
→重设项目文件夹
路径选择E:\RE_Client_Code\Assets\Data
2.3SK框架
→脚本
→重新生成wrap文件
- 在Unity上运行自己的服务器
修改RE_Client_Code\Assets\Data\Scripts\game\module\LoginConfig.lua
文件 - 搜索
Game
应用,选中并启动