欢迎参与讨论,转载请注明出处。
前言
书接上文,这次要讲的是服务端。《Brick & Ball》(下称BNB)所需要的网络功能仅为匹配与联机对战而已,所以在最初对此是轻视的,在尝试了UNet与Photon之后感觉各有硬伤(UNet不支持纯服务端架设、Photon服务端为Windows),遂放弃了这些看似完备的服务端套件,转为使用小有名气的Skynet后,不负所望地顺利完成了,于此做个总结。
结构
在阅读本文之前,你需对Skynet有个大致的了解。Skynet的业务单位称为服务,它是一种Actor模型的实现。以下是BNB服务端的服务结构:
- Gate: 网关服务,负责管理用户(接入、踢出、发信、心跳包)。
- Queue: 队列服务,负责用户的匹配,当有新的用户到来就会进入队列。
- Lobby: 大厅服务,负责Room的管理(创建、关闭),完成匹配的用户就会为他们创建Room。
- Room: 房间服务,负责用户的游戏提供(帧锁定同步)。
KCP
对于BNB这种高实时性的游戏,自然是不方便使用TCP了(三次握手、非快速重传、滑动窗口),然而直接使用UDP又会有可靠性的问题(丢包、非顺序到达),业界流行的做法一般是在UDP的基础上实现重传保证可靠性,而其中比较著名的实现则是KCP(再次感谢Skywind!)。KCP虽然是用C语言实现的,但还是有C#和Lua的移植与封装的版本。
封包
在封包的设计上我图省事使用了JSON,并在封包的首部使用了1字节作为标志,并未考虑加密(因为觉得没有意义)。C#方面直接使用内置函数解决,Lua则是使用了CJSON,字节处理则是使用Lua5.3新增的string.pack
与string.unpack
函数(非5.3需安装struct)。
接入用户
按理来说客户端与服务端的初次连接使用TCP更为适合(一个KCP对象只服务一个连接,所以初次连接的客户端在服务端并没有对应的KCP对象),但是为了偷懒我采用了这样的方式:
对于不在用户列表的来源,则直接判定该包尾部1字节是否等于_ID.connect
(KCP会在封包的头部添加信息,所以在没有JSON内容的情况下,该包尾部则是原封包的头部),这种野蛮的方式缺点自然是只能填写标识而不能添加JSON。所以如果客户端还需要一些信息的话还需要收到回执后补充,当然事实上就需要回执:存在着因版本不对、服务器爆满的情况而拒绝连接的情况。所以服务端需要回执后方正式将其接入。
心跳包
鉴于UDP的无连接特性,是无法判断用户是否掉线的(事实上TCP的机制也非完美)。业界通行的做法是做心跳包,即每隔一段时间进行通信以确定对方仍存活。BNB采用的方式是礼尚往来(客户端每隔一段时间发送心跳包,服务端收到后发送回执),即双端皆有心跳状态:在客户端看来,无论是超时没有收到心跳包、亦或是自身无法发出心跳包,都视为掉线。在服务端看来,只要该客户端超时没有发过心跳包,即踢出之:
|
|
|
|
匹配队列
BNB的匹配规则就是没有规则,匹配到了两名玩家就开始游戏,所以只需设计一个队列即可。每逢有新用户接入后便会进入队列,如用户离去则从队列消除,若匹配成功则为他们创建一场游戏:
|
|
创建游戏
在匹配完成后,便会由Lobby服务为一对用户创建房间(Room服务),在此之前会对两名用户是否在线进行检查,若不满足则将两名用户进行踢出,需重新进行连接:
|
|
创建房间之后,会为对应的客户端发送开始游戏所需的数据(随机数种子、双方阵营所属)。待客户端初始化完毕后,游戏正式开始:
|
|
|
|
帧锁定同步
如上文所言(在采用传统帧锁定同步的基础上,服务端设定等待时长,超时则继续),服务端的业务设计成当接收到一名用户的输入包后,就会开始进行计时(9毫秒,约等于客户端的5帧,即WAITTING_INTERVAL)。若超时或在时间内抵达第二名用户的输入包,则进行结算(广播用户们的输入数据)。用户的输入数据在服务端是作为缓存式的,超时了也会进行记录,作为下一次结算所用,每次结算后输入数据则会清空:
|
|
维护
服务端不同于客户端,发生错误使程序崩溃的代价是很大的,所以有必要建立完善的应对措施。所幸目前发现Skynet发生错误时会影响的仅为Skynet.fork
的函数(发生错误后函数会停止运行),并不会导致整个服务崩溃乃至进程崩溃。于是只要利用Lua的pcall(func, ...)
函数进行异常处理即可:
|
|
当然遇到问题仅仅是堵住那只是治标不治本,所以我采用了邮件报警机制。只要在config
文件填写mail
,然后调用_SKYNET.Warn()
即会发送到目标邮箱,且整个进程生命周期内只会发送一次,避免疯狂轰炸的情况:
|
|
虽然理论上没有会令Skynet进程崩溃的情况,但以防万一,还是专门做了崩溃重启的措施:
|
|
还有一点就是,帧锁定同步的浮点数问题并不是那么令人放心的存在。所以有必要对其进行监控(这个在上文也有提到),同理遇到异常情况也会进行邮件报警:
|
|
后记
这次是本人初次进行服务端开发,如有不妥之处但请指教。虽无涉及数据库、反作弊、集群、运维等方面,但也不失为一个匹配-房间-帧锁定同步的好范例。