第三方游戏后台
Photon
https://www.photonengine.com/en/sdks
http://www.gameres.com/797805.html
smart fox server
Socket
Socket.io
1. namespace
Server (app.js)
1 | var io = require('socket.io')(80); |
Client (index.html)
1 | <script> |
游戏网络
浅谈RTS游戏网络同步:3种同步机制模式的实现
1. 同步机制
假设游戏中A,B两个玩家移动,并同时向对方发出射击指令,如果没有合适的同步机制,那么可能出现的情况有:
- A屏幕显示B已经被杀死,B屏幕显示A已经被杀死
- 或者在瞄准后确打不到对方
同步机制最重要的作用就是解决延迟等可能发生不一致的情况。
2. 同步机制的分类
A Peer-to-peer模式:
没有服务器,每个玩家互相连接,各自模拟整个流程.典型的lockstep模式
- 优点:减少主机带来的延时
- 缺点:容易作弊
Client-Server模式
所有的操作需经过服务器确认后才能进行客户端模拟,如arpg传奇类都是此架构,如果延时高就会有明显的卡顿。
- 优点:服务器是绝对的权威,可以防止作弊,可以做更多的管理与限制
- 缺点:服务器变的更复杂,服务器断线,所有玩家断线,属于服务器依赖型。
3. 标准的lockstep模式
每个玩家互相连接,整个游戏过程划分成一组turn指令帧,由玩家自我模拟
游戏速度取决于网络最慢的那个玩家
一个玩家掉线不会影响到其他玩家
什么是Turn?
一个turn可以理解成1个回合,相信大家都玩过回合制游戏吧,只是这个turn非常短,大概100MS-200MS。玩家相互之间发送的指令在每个turn间隔发出。
每个玩家只需要接收指令,并在本地播放指令就可以啦。
实际上玩家都只需要发送基本的指令如选择单位,移动单位,使用技能1234,点击物品栏1-6,可以通过APM查看软件看到一些基本操作事件。
也就是说所有的一切伤害计算都是在本地计算完成的。
包括伤害,暴击,命中,刷怪等,只要初始化好随机数种子就可以啦。
玩家只是发送操作指令,如点击坐标(0,1, 0),左键框选(100,100,50,50)等。
每个玩家都在模拟全部的流程。
4. Client-Server的一种的特殊模式
主机负责广播每个client的指令,
这存在两个问题:
- 本机(非主机)发出的指令,如果超时或者丢包,是否直接丢弃?
- 其他玩家的指令,主机转发未成功确认,如何处理?
- 如果是本机(非主机)发出的指令超时,可以直接丢弃。(如果不丢弃,其他玩家就必须等待结果,这样会导致挂起,而且会非常频繁,这里还有udp协议容易丢包的原因,但是war3好像并没有经常性的挂起)
还有一种可能,客户端得知之前的turn没有发送成功,把当前这轮的指令和上一轮的指令进行合并,然后一起发出,这样本地客户端就不会有任何的异样了。
例如玩家移动到A后再移动到B
上个turn的指令是移动到A点,但是没有发成功,下个turn的指令先移动到A,再移动到B,这样在客户端就不会有丢失的感觉啦,还是可以正常的模拟而不会影响到其他玩家。
- 收其他玩家的指令超时,那么属于我们自身网络的问题,如果丢弃必将导致游戏进程不同步,所以服务器必须将他们的turn指令都缓存起来,
或者缓存一部分turn指令集,在我网络稳定的时候,把丢失的那一部分turn指令集发给我,而我只需要下载那个list加快gameupdate就好啦。
实时动作游戏同步方式和传输协议选择
1. 网速的变化
开发快速动作游戏,首先要对公网的网络质量数据有详细的了解。这里所说到的网速,是指 RTT,数据往返一周的毫秒时间,而非每秒传送多少 KB/s。
- 2005-2006年国内的网络环境,公网 RTT平均值基本在100ms,120ms左右徘徊。
- 2009 年以后已经好了很多(60ms的rtt)。
- 2012年以后,公网平均 RTT已经降低到平均 40ms-50ms,省内平均10ms以内了
2. 帧间同步法
关于帧间同步的”帧锁定算法”系列的方法有很多类似实现(包括后面提到的帧间无等待改进,包括 LockStep等),但是他们的核心都是一个:保证所有客户端每帧的输入都一样。这样的方式被格斗游戏,RTS和足球(FIFA类)、篮球(NBA)等体育和动作游戏大量使用,比如我们熟悉的各大战网平台游戏(Xbox Live等),还有很多基于模拟器的街机对战平台。以及不少大型多人横版动作游戏。以开发便利,同步逻辑直观而受到大家欢迎。
帧锁定算法多用在 C/S模型中(或者一人做主多人做从的P2P里),它和 LockStep(多用于P2P)共同存在的问题就是 “网速慢的玩家会卡到网速快的玩家”,老式游戏经常一个角色断网,所有人就在那里等待。为此出现了帧锁定的改良版本 “乐观帧锁定”(具体描述见帧锁定文章的下半部分)经过了不少游戏的实践检验。先前还有几款上线的横版格斗页游(如熟知的街机三国)用 Flash 的 TCP without NODELAY 来每秒20个关键帧的模式(特意找该游戏开发者确认了一下)跑该算法(由于近两年国内网速提高,Flash的 Tcp without NODELAY也能做很多事情了),效果还不错。
具体实施时用不着按照文所述每一个步奏都相同,可以有很多变通。比如不一定是有变化的时候才通知服务端,有线上某横版格斗页游就是也可以每秒 20次向服务端直接发送数据(flash时钟不准需要自己独立计时),服务端再每秒 40次更新回所有客户端,看具体情况而定。
也有使用 UDP的端游,客户端每秒钟上传50次键盘信息到服务端,丢了就丢了,后面持续发送过来的键盘数据会覆盖前面的数据,所以丢了没关系,更快捷。当然,UDP也不是必须的,近两年网速提高很快,省内都能做到10ms的 RTT 了,跨省也就 50ms的rtt,不少页游上用该方法上裸的 TCP 照样跑的很顺畅。
而近两年国外动作游戏领域也涌现出其他一些新的改良方法,比如 Time Warp,以客户端先行+逻辑不一致时回滚的方式,带来了更好的同步效果,俗称时间回退法。不果国内暂时没看到有游戏这么尝试,更多的是国外近两年的双人动作游戏比较多,要求游戏每帧状态都可以保存,逻辑上开发会复杂一些。国内大部分是超过两人出去副本的,在3-4人出去 PK的情况下,引入状态回退,会让整个效果大打折扣。不过2人的效果确实有所改进,有兴趣的同学可以搜索 Time Warp相关的论文。
2009年,云游戏(游戏远程渲染)技术得到广泛应用,客户端上传操作,服务端远程渲染,并以低延迟视频编码流的方式传回给客户端,用的就是这样类似的技术。客户端不需要高额的硬件,也不存在盗版问题,其中 Gaikai和 OnLive两家公司做的比较好。
2012年,Sony推出 Playstation Now技术,可以在 PSV和 PS3/PS4上玩云游戏,玩家不需要购买游戏就可以免费体验一定时间。使得 PSV/PS3等低端硬件也可以流畅的跑 PS4游戏。
但是目前国外网络环境下跑的还比较流畅,国内的网络环境要低延迟传送 HD画质的视频流还比较困难,视频都是比较费带宽的。但是帧锁定等保证每帧输入一致的算法,在当今的网络质量下传递一下玩家操作,还是没有任何问题的。
3. 状态同步法
对于逻辑不需要精确到帧的游戏类型而言(RPG/ARPG,FPS,赛车),允许每个客户端屏幕上显示的内容不同,只要将他们统一到一个逻辑中即可,这部分见:”网络游戏同步法则”(最好给策划看看这篇,从玩法上规避)。如果是 RPG游戏,其实更多是使用障眼法从玩法和动画效果上减少 “一次性的”,”决定性”的事件即可:
RPG 游戏的移动很简单,只需要”谁在哪里朝着哪里移动”,客户端再做一些简单的平滑处理即可,不需要额外的”时间”参数。比如《魔兽世界》移动时,就是差不多每秒发送一次(坐标,朝向,速度),别的客户端收到以后就会矫正一下,如果矫正错误,比如 A本来往北走突然拐弯向东,这个数据包传到B上,B屏幕上的A可能在拐弯前往北跑了更远,致使拐弯向东时被树卡住,那么B就会看到A被树卡了两秒无法移动,然后突然瞬间移动到新的坐标,继续朝着东跑。
通常 RPG攻击分为”有锁定攻击”和”无锁定攻击”,有锁定攻击意思是,我朝你发射火球,不管你怎么跑,火球都会追踪并射击到你,比如你在我面前横着跑过,我向你发射火球,可以发现火球并不是直线飞行,而是曲线追踪着你就过去了,这叫有锁定攻击。无锁定攻击一般是范围攻击,先播放个动画(比如挥刀),然后将攻击请求提交服务器,服务器结果回来时,动画刚好播放完毕,然后大家一起减血。
而 FPS和 赛车类游戏的同步性要求比 RPG高很多,每秒发包量也会多很多(10-30个),多半采用位置预测及坐标差值的”导航推测算法(DR)”,具体实现见我的:”影子跟随算法”(DR算法的一个改进实现)。
这类算法由于位置判定更为精确,所以计算量大,很多没法服务端判断,而是客户端直接判断,比如 FPS射击是否打到别人,客户端先判断,除了狙击这种一枪毙命的射击外基本都是客户端判断的。由于计算更为复杂,每秒同步发包差不多到 30个以上,这样的模式下,每局游戏的人数也不可能很多,一般16人左右。而且很多才用 P2P的方式运行,具体 FPS游戏的实现,及 DR算法的代码编写,见 “影子跟随算法”这篇文章。
其实状态同步是一种乐观的同步方法,认为大家屏幕上的东西不同没关系,只要每次操作的结果相同即可,不需要象”帧间同步”那样保证每帧都一样,因此,对网速的要求也没有 “帧间同步”系列算法那么苛刻,一般100ms-200ms都是能够接受的(DiabloIII里面300ms的延迟照样打),偶尔网络抖一下,出现1秒的延迟,也能掩盖过去。然而比起 “帧间同步”,状态同步方式对玩法有不少要求,诸如 “一次性”,”决定性”的事件要少很多,而且代码编写会复杂一些,不果由于能容忍更坏的网络情况,以及容纳更多同时游戏的人数,在一些玩法确定的游戏中(RPG,FPS,赛车),被广泛使用。
而状态同步又分为”DR同步”和”非DR同步”,前者针对 FPS,赛车或者更激烈点的 ARPG,后者针对 RPG和普通 ARPG。他们对网速的要求和错误的容忍度也是不一样,当然,带来的游戏即时感也是不同的。
总得来说,你希望游戏体验更爽快,即时感更强,那么你每秒发包数就越多,每局(副本)支持的人数越少;而你如果追求对网络的容忍,想降低发包数,并且增加同时游戏的人数,那么相应的就需要以降低即时感为代价,其二者不可得兼。然而聪明的策划和程序们总能想出很多好主意,利用障眼法和玩法规避,动作掩盖等方法,在相同的情况下来掩盖延迟,让玩家”看起来”更加”即时”和”爽快”,而这个方法具体该怎么做,并没有统一的做法,就得大家结合自己的游戏和玩法,发挥自己的聪明才智了。
4. 结果同步法
结果同步往往比较简单,位置即使全部错乱或者延迟很久都没有关系,因为游戏过程完全不在乎位置,只在乎最后的结果,比如《梦幻西游》这样的”回合制 RPG” 游戏,屏幕上的人走到哪里确实无所谓,所有操作都是要点击或者选择菜单来下命令,象这样的游戏背后其实是文字游戏,只是加了一个图形的壳。
游戏表面上看起来是动作/RTS 游戏,但是没有玩家直接协作和对抗,都是单机游戏,并不需要同步什么东西,服务端只要监测下结果不离谱即可,延迟检测都没关系。基本是 PVE,而且无协作。即使是 PVP也就是打一下别人的离线数据,和无同步回合制游戏并无本质上的区别。
5. 传输协议选择
老话题 TCP还是 UDP,答案是大部分时候,TCP打开 NODELAY即可,现在网络情况好了很多,没必要引入新的复杂度。即便是”帧锁定算法”上线的多人实时格斗游戏,也有在用 TCP跑着的。帧间同步如果能够做到更好的架设机房,那么延迟基本能控制在 10ms以内,将游戏玩家按照区域分服务器,让他们选择更快的服务器。
即便是带 DR的状态同步,很多也都是 TCP的,《魔兽世界》和《暗黑破坏神3》都是基于 TCP来实现的,所以我的建议是,先上 TCP,把你的游戏发布出去。
当然,等到你的游戏发布出去了,开始挣钱了,你想改进你的游戏效果,特别是高峰期的卡顿比例(需要收集客户端统计),那么你可以使用 UDP来改进,《街霸4》和《英雄联盟》都是使用 UDP的,比如你可以使用 libenet(英雄联盟用的)。不过 libenet所采用的传输技术,是上世纪的标准 ARQ做法了,《街霸4》所采用的传输技术远远高过 libenet,如果你想采用更为现代的传输技术,赢得更低延迟的话,可以使用我的”快速传输协议-KCP”(http://www.skywind.me/blog/archives/1048),被再若干上线项目和开源项目使用的协议,效果远远 PK libenet。
在使用 KCP时,你可以用在你 TCP的基础上,再登陆时服务端返回 UDP端口和密钥,客户端通过 TCP收到以后,向服务端的 UDP端口每隔一秒重复发送包含握手信息,直到服务端返回成功或者失败。服务端通过 UDP传上来的密钥得知该客户端 sockaddr对应的 TCP连接,这样就建立 TCP连接到 UDP连接的映射关系。为了保持连接和 NAT出口映射,客户端一般需要每 60秒就发送一个 UDP心跳,服务端收到后回复客户端,再在这个 UDP连接的基础上增加调用 KCP的逻辑,实现快速可靠传输,这样一套 TCP/UDP两用的传输系统就建立了。
中国的网络情况比较特殊,会存在有些网络 UDP连接不上的情况,因此都是先连接 TCP,然后试图 UDP,UDP不通的情况下,退回 TCP也能正常游戏,一旦 TCP断开,则认为 UDP也断开了。
不果归根结底,还是先上 TCP,再根据自己游戏的特点和是否出现传输问题,选择 UDP。
6. 话题总结
根据游戏类型,选择恰当的同步方式和传输协议是最基础的问题,很多讲述网络同步的文章一般就是只会强调上述那么多种算法的其中一种方式,好像使用该方式就可以 hold住所有游戏一样的,其实并非如此。技术需要多和策划沟通,别策划一个需求下来,技术就来一句:无法实现,这样的游戏永远没有竞争力。就像国内当时都是慢节奏 RPG,偶尔有点 ARPG的时候,大家觉得《DNF》这样的游戏无法实现,于是韩国实现了,在市场上取得了先机,国内才慢慢跟进,再一看,哇塞,好多坑呢。
然后开发者开始在网上寻找各种同步算法,东一榔头西一棒子,明明是一款需要帧间同步的格斗游戏,结果却上了导航推测,最后发现问题永远解决不了,一堆 BUG这就叫误导。正因为我 2004年就开始弄同步相关的问题,期间也指导过不少游戏设计他们的同步方案,所以这次相当于将以前的观点做一个总结和点评,根据自己的游戏类型选择最适合的同步算法,为玩家提供更好的体验才是关键。
手机格斗网游该如何避免延迟(帧锁定同步算法)
1. 算法概念
该算法普遍要求网速RTT要在100ms以内,一般人数不超过8人,在这样的情况下,可以像单机游戏一样编写网络游戏。所有客户端任意时刻逻辑都是统一的
缺点是一个人卡机,所有人等待。
- 客户端定时(比如每五帧)上传控制信息。
- 服务器收到所有控制信息后广播给所有客户。
- 客户端用服务器发来的更新消息中的控制信息进行游戏。
- 如果客户端进行到下一个关键帧(5帧后)时没有收到服务器的更新消息则等待。
- 如果客户端进行到下一个关键帧时已经接收到了服务器的更新消息,则将上面的数据用于游戏,并采集当前鼠标键盘输入发送给服务器,同时继续进行下去。
- 服务端采集到所有数据后再次发送下一个关键帧更新消息。
这个等待关键帧更新数据的过程称为”帧锁定”
应用案例:大部分RTS游戏,街霸II(xbox360),Callus模拟器。
2. 算法流程
客户端逻辑:
- 判断当前帧F是否关键帧K1:如果不是跳转(7)。
- 如果是关键帧,则察看有没有K1的UPDATE数据,如果没有的话重复2等待。
- 采集当前K1的输入作为CTRL数据与K1编号一起发送给服务器
- 从UPDATE K1中得到下一个关键帧的号码K2以及到下一个关键帧之间的输入数据I。
- 从这个关键帧到下 一个关键帧K2之间的虚拟输入都用I。
- 令K1 = K2。
- 执行该帧逻辑
- 跳转(1)
服务端逻辑:
- 收集所有客户端本关键帧K1的CTRL数据(Ctrl-K)等待直到收集完成所有的CTRL-K。
- 根据所有CTRL-K,计算下一个关键帧K2的Update,计算再下一个关键帧的编号K3。
- 将Update发送给所有客户端
- 令K1=K2
- 跳转(1)
服务器根据所有客户端的最大RTT,平滑计算下一个关键帧的编号,让延迟根据网络情况自动调整。
3. 算法演示
我根据该算法将街机模拟器修改出了一个可用于多人对战的版本,早期有一个叫做kaillera的东西,可以帮助模拟器实现多人联机,但是并没有作帧锁定,只是简单将键盘消息进行收集广播而已,后来Capcom在PSP和360上都出过街霸的联网版本,但是联网效果不理想。这个算法其实局域网有细就经常使用了,只是近年来公网速度提高,很容易找到RTT < 50ms的服务器,因此根据上述算法,在平均RTT=100ms(操作灵敏度1/10秒),情况下,保证自动计算关键帧适应各种网络条件后,就能够像编写单机游戏一样开发网游,而不需状态上作复杂的位置/状态同步。
从上图的演示中可以看到,两个模拟器进程都在运行1941这个游戏,两边客户端使用了该算法,将逻辑统一在一个整体中。
最后这张图是运行KOF99的效果图,两边完美同步,上图是我开发的街机对战效果,在公网环境下,只要不是小型宽带用户,一般电信或者联通用户都能有一个比较好的体验。
4. 乐观帧锁定
针对传统帧锁定算法网速慢会卡到网速快的问题,实践中线上动作游戏通常用”定时不等待”的方式再每次Interval时钟发生时固定将操作广播给所有用户,不依赖具体每个玩家是否有操作更新:
- 单个用户当前键盘上下左右攻击跳跃是否按下用一个32位整数描述,服务端描述一局游戏中最多8玩家的键盘操作为:int player_keyboards[8];
- 服务端每秒钟50次向所有客户端发送更新消息(包含所有客户端的操作和递增的帧号): update=(FrameID,player_keyboards)
- 客户端就像播放游戏录像一样不停的播放这些包含每帧所有玩家操作的update消息。
- 客户端如果没有update数据了,就必须等待,直到有数据到来。
- 客户端如果一下子收到很多连续的update,则快进播放。
- 客户端只有按键按下或者放开,就会发送消息给服务端(而不是到每帧开始才采集键盘),消息只包含一个整数。服务端收到以后,改写player_keyboards
虽然网速慢的玩家网络一卡,可能就被网速快的玩家给秒了(其他游戏也差不多)。但是网速慢的玩家不会卡到快的玩家,只会感觉自己操作延迟而已。另一个侧面来说,土豪的网宿一般比较快,我们要照顾。
5. 指令缓存
针对高级别的抽象指令(非前后可以覆盖的键盘操作),比如即时战略游戏中,各种高级操作指令,在”乐观帧锁定”中,客户端任何操作都是可靠消息发送到服务端,服务端缓存在对应玩家的指令队列里面,然后定时向所有人广播所有队列里面的历史操作,广播完成后清空队列,等待新的指令上传。客户端收到后按顺序执行这些指令,为了保证公平性,客户端可以先执轮询行每个用户的第一条指令,执行完以后弹出队列,再进入下一轮,直到没有任何指令。这样在即时战略游戏中,选择 250ms一个同步帧,每秒四次,已经足够了。如果做的好还可以象 AOE一样根据网速调整,比如网速快的时候,进化为每秒10帧,网速慢时退化成每秒4帧,2帧之类的。
PS:可以把整段战斗过程的操作和随机数种子记录下来,不但可以当录像播放,还可以交给另外一台服务端延迟验算,还可以交给其他空闲的客户端验算,将验算结果的 hash值进行比较,如果相同则认可,如果不通则记录或者处理,服务端如果根据游戏当前进程加入一些临时事件(比如天上掉下一个宝箱),可以在广播的时候附带。
游戏服务端架构发展史
类型1:卡牌,跑酷等弱交互服务端
卡牌跑酷类因为交互弱,玩家和玩家之间不需要实时面对面PK,打一下对方的离线数据,计算下排行榜,买卖下道具即可,所以实现往往使用简单的 HTTP服务器:
登录时可以使用非对称加密(RSA, DH),服务器根据客户端uid,当前时间戳还有服务端私钥,计算哈希得到的加密 key 并发送给客户端。之后双方都用 HTTP通信,并用那个key进行RC4加密。客户端收到key和时间戳后保存在内存,用于之后通信,服务端不需要保存 key,因为每次都可以根据客户端传上来的 uid 和 时间戳 以及服务端自己的私钥计算得到。用模仿 TLS的行为,来保证多次 HTTP请求间的客户端身份,并通过时间戳保证同一人两次登录密钥不同。
每局开始时,访问一下,请求一下关卡数据,玩完了又提交一下,验算一下是否合法,获得什么奖励,数据库用单台 MySQL或者 MongoDB即可,后端的 Redis做缓存(可选)。如果要实现通知,那么让客户端定时15秒轮询一下服务器,如果有消息就取下来,如果没消息可以逐步放长轮询时间,比如30秒;如果有消息,就缩短轮询时间到10秒,5秒,即便两人聊天,延迟也能自适应。
此类服务器用来实现一款三国类策略或者卡牌及酷跑的游戏已经绰绰有余,这类游戏因为逻辑简单,玩家之间交互不强,使用 HTTP来开发的话,开发速度快,调试只需要一个浏览器就可以把逻辑调试清楚了
类型2:第一代游戏服务器 1978
1978年,英国著名的财经学校University of Essex的学生 Roy Trubshaw编写了世界上第一个MUD程序《MUD1》,在University of Essex于1980年接入 ARPANET之后加入了不少外部的玩家,甚至包括国外的玩家。《MUD1》程序的源代码在 ARPANET共享之后出现了众多的改编版本,至此MUD才在全世界广泛流行起来。不断完善的 MUD1的基础上产生了开源的 MudOS(1991),成为众多网游的鼻祖:
MUDOS采用 C语言开发,因为玩家和玩家之间有比较强的交互(聊天,交易,PK),MUDOS使用单线程无阻塞套接字来服务所有玩家,所有玩家的请求都发到同一个线程去处理,主线程每隔1秒钟更新一次所有对象(网络收发,更新对象状态机,处理超时,刷新地图,刷新NPC)。
游戏世界采用房间的形式组织起来,每个房间有东南西北四个方向可以移动到下一个房间,由于欧美最早的网游都是地牢迷宫形式的,因此场景的基本单位被成为 “房间”。MUDOS使用一门称为LPC的脚本语言来描述整个世界(包括房间拓扑,配置,NPC,以及各种剧情)。游戏里面的高级玩家(巫师),可以不断的通过修改脚本来为游戏添加房间以及增加剧情。早年 MUD1上线时只有17个房间,Roy Trubshaw毕业以后交给他的师弟 Richard Battle,在 Richard Battle手上,不断的添加各种玩法到一百多个房间,终于让 MUD发扬光大。
用户使用 Telnet之类的客户端用 Tcp协议连接到 MUDOS上,使用纯文字进行游戏,每条指令用回车进行分割。比如 1995年国内第一款 MUD游戏《侠客行》,你敲入:”go east”,游戏就会提示你:”后花园 – 这里是归云庄的后花园,种满了花草,几个庄丁正在浇花。此地乃是含羞草生长之地。这里唯一的出口是 north。这里有:花待 阿牧(A mu),还有二位庄丁(Zhuang Ding)”,然后你继续用文字操作,查看阿牧的信息:”look a mu”,系统提示:”花待 阿牧(A mu)他是陆乘风的弟子,受命在此看管含羞草。他看起来三十多岁,生得眉清目秀,端正大方,一表人才。他的武艺看上去【不是很高】,出手似乎【极轻】”。然后你可以选择击败他获得含羞草,但是你吃了含羞草却又可能会中毒死亡。在早期网上资源贫乏的时候,这样的游戏有很强的代入感。
用户数据保存在文件中,每个用户登录时,从文本文件里把用户的数据全部加载进来,操作全部在内存里面进行,无需马上刷回磁盘。用户退出了,或者每隔5分钟检查到数据改动了,都会保存会磁盘。这样的系统在当时每台服务器承载个4000人同时游戏,不是特别大的问题。从1991年的 MUDOS发布后,全球各地都在为他改进,扩充,退出新版本,随着 Windows图形机能的增强。1997游戏《UO》在 MUDOS的基础上为角色增加的x,y坐标,为每个房间增加了地图,并且为每个角色增加了动画,形成了第一代的图形网络游戏。
因为游戏内容基本可以通过 LPC脚本进行定制,所以MUDOS也成为名副其实的第一款服务端引擎,引擎一次性开发出来,然后制作不同游戏内容。后续国内的《万王之王》等游戏,很多都是跟《UO》一样,直接在 MUDOS上进行二次开发,加入房间的地图还有角色的坐标等要素,该架构一直为国内的第一代 MMORPG提供了稳固的支持,直到 2003年,还有游戏基于 MUDOS开发。
虽然后面图形化增加了很多东西,但是这些MMORPG后端的本质还是 MUDOS。随着游戏内容的越来越复杂,架构变得越来越吃不消了,各种负载问题慢慢浮上水面,于是有了我们的第二代游戏服务器。
类型3:第二代游戏服务器 2003
2000年后,网游已经脱离最初的文字MUD,进入全面图形化年代。最先承受不住的其实是很多小文件,用户上下线,频繁的读取写入用户数据,导致负载越来越大。随着在线人数的增加和游戏数据的增加,服务器变得不抗重负。同时早期 EXT磁盘分区比较脆弱,稍微停电,容易发生大面积数据丢失。因此第一步就是拆分文件存储到数据库去:
此时游戏服务端已经脱离陈旧的 MUDOS体系,各个公司在参考 MUDOS结构的情况下,开始自己用 C在重新开发自己的游戏服务端。并且脚本也抛弃了 LPC,采用扩展性更好的 Python或者 Lua来代替。由于主逻辑使用单线程模型,随着游戏内容的增加,传统单服务器的结构进一步成为瓶颈。于是有人开始拆分游戏世界,变为下面的模型:
游戏服务器压力拆分后得意缓解,但是两台游戏服务器同时访问数据库,大量重复访问,大量数据交换,使得数据库成为下一个瓶颈。于是形成了数据库前端代理(DB Proxy),游戏服务器不直接访问数据库而是访问代理,再有代理访问数据库,同时提供内存级别的cache。早年 MySQL4之前没有提供存储过程,这个前端代理一般和 MySQL跑在同一台上,它转化游戏服务器发过来的高级数据操作指令,拆分成具体的数据库操作,一定程度上代替了存储过程:
但是这样的结构并没有持续太长时间,因为玩家切换场景经常要切换连接,中间的状态容易错乱。而且游戏服务器多了以后,相互之间数据交互又会变得比较麻烦,于是人们拆分了网络功能,独立出一个网关服务 Gate(有的地方叫 Session,有的地方叫 LinkSvr之类的,名字不同而已):
但是这样的结构并没有持续太长时间,因为玩家切换场景经常要切换连接,中间的状态容易错乱。而且游戏服务器多了以后,相互之间数据交互又会变得比较麻烦,于是人们拆分了网络功能,独立出一个网关服务 Gate(有的地方叫 Session,有的地方叫 LinkSvr之类的,名字不同而已):
把网络功能单独提取出来,让用户统一去连接一个网关服务器,再有网关服务器转发数据到后端游戏服务器。而游戏服务器之间数据交换也统一连接到网管进行交换。这样类型的服务器基本能稳定的为玩家提供游戏服务,一台网关服务1-2万人,后面的游戏服务器每台服务5k-1w,依游戏类型和复杂度不同而已,图中隐藏了很多不重要的服务器,如登录和管理。这是目前应用最广的一个模型,到今天任然很多新项目会才用这样的结构来搭建。
人都是有惯性的,按照先前的经验,似乎把 MUDOS拆分的越开性能越好。于是大家继续想,网关可以拆分呀,基础服务如聊天交易,可以拆分呀,还可以提供web接口,数据库可以拆分呀,于是有了下面的模型:
这样的模型好用么?确实有成功游戏使用类似这样的架构,并且发挥了它的性能优势,比如一些大型 MMORPG。但是有两个挑战:每增加一级服务器,状态机复杂度可能会翻倍,导致研发和找bug的成本上升;并且对开发组挑战比较大,一旦项目时间吃紧,开发人员经验不足,很容易弄挂。
比如我见过某上海一线游戏公司的一个 RPG上来就要上这样的架构,我看了下他们团队成员的经验,问了下他们的上线日期,劝他们用前面稍微简单一点的模型。人家自信得很,认为有成功项目是这么做的,他们也要这么做,自己很想实现一套。于是他们义无反顾的开始编码,项目做了一年多,然后,就没有然后了。
现今在游戏成功率不高的情况下,一开始上一套比较复杂的架构需要考虑投资回报率,比如你的游戏上线半年内 PCU会去到多少?如果一个 APRG游戏,每组服务器5千人都到不了的话,那么选择一套更为贴近实际情况的结构更为经济。即使后面你的项目真的超过5千人朝着1万人目标奔的话,相信那个时候你的项目已经挣大钱了 ,你数着钱加着班去逐步迭代,一次次拆分它,相信心里也是乐开花的。
上面这些类型基本都是从拆分 MUDOS开始,将 MUDOS中的各个部件从单机一步步拆成分布式。虽然今天任然很多新项目在用上面某一种类似的结构。因为他们本质上都是对 MUDOS的分解,故将他们归纳为第二代游戏服务器。
类型4:第三代游戏服务器 2007
从魔兽世界开始无缝世界地图已经深入人心,比较以往游戏玩家走个几步还需要切换场景,每次切换就要等待 LOADING个几十秒是一件十分破坏游戏体验的事情。于是对于 2005年以后的大型 MMORPG来说,无缝地图已成为一个标准配置。比较以往按照地图来切割游戏而言,无缝世界并不存在一块地图上面的人有且只由一台服务器处理了:
每台 Node服务器用来管理一块地图区域,由 NodeMaster(NM)来为他们提供总体管理。更高层次的 World则提供大陆级别的管理服务。这里省略若干细节服务器,比如传统数据库前端,登录服务器,日志和监控等,统统用 ADMIN概括。在这样的结构下,玩家从一块区域走向另外一块区域需要简单处理一下:
玩家1完全由节点A控制,玩家3完全由节点B控制。而处在两个节点边缘的2号玩家,则同时由A和B提供服务。玩家2从A移动到B的过程中,会同时向A请求左边的情况,并向B请求右边的情况。但是此时玩家2还是属于A管理。直到玩家2彻底离开AB边界很远,才彻底交由B管理。按照这样的逻辑将世界地图分割为一块一块的区域,交由不同的 Node去管理。
对于一个 Node所负责的区域,地理上没必要连接在一起,比如大陆的四周边缘部分和高山部分的区块人比较少,可以统一交给一个Node去管理,而这些区块在地理上并没有联系在一起的必要性。一个 Node到底管理哪些区块,可以根据游戏实时运行的负载情况,定时维护的时候进行更改 NodeMaster 上面的配置。
于是碰到第一个问题是很多 Node服务器需要和玩家进行通信,需要问管理服务器特定UID为多少的玩家到底在哪台 Gate上,以前按场景切割的服务器这个问题不大,问了一次以后就可以缓存起来了,但是现在服务器种类增加不少,玩家又会飘来飘去,按UID查找玩家比较麻烦;另外一方面 GATE需要动态根据坐标计算和哪些 Node通信,导致逻辑越来越厚,于是把:”用户对象”从负责连接管理的 GATE中切割出来势在必行于是有了下面的模型:
网关服务器再次退回到精简的网络转发功能,而用户逻辑则由按照 UID划分的 OBJ服务器来承担,GATE是按照网络接入时的负载来分布,而 OBJ则是按照资源的编号(UID)来分布,这样和一个用户通信直接根据 UID计算出 OBJ服务器编号发送数据即可。而新独立出来的 OBJ则提供了更多高层次的服务:
- 对象移动:管理具体玩家在不同的 Node所管辖的区域之间的移动,并同需要的 Node进行沟通。
- 数据广播:Node可以给每个用户设置若干标签,然后通知 OBJ Master 按照标签广播。
- 对象消息:通用消息推送,给某个用户发送数据,直接告诉 OBJ,不需要直接和 GATE打交道。
- 好友聊天:角色之间聊天直接走 OBJ/OBJ MASTER。
整个服务器主体分为三层以后,NODE专注场景,OBJ专注玩家对象,GATE专注网络。这样的模型在无缝场景服务器中得到广泛的应用。但是随着时间的推移,负载问题也越来越明显,做个活动,远来不活跃的区域变得十分活跃,靠每周维护来调整还是比较笨重的,于是有了动态负载均衡。
动态负载均衡有两种方法,第一种是按照负载,由 Node Master 定时动态移动修改一下各个 Node的边界,而不同的玩家对象按照先前的方法从一台 Node上迁移到另外一台 Node上:
这样 Node Master定时查找地图上的热点区域,计算新的场景切割方式,然后告诉其他服务器开始调整,具体处理方式还是和上面对象跨越边界移动的方法一样。
但是上面这种方式实现相对复杂一些,于是人们设计出了更为简单直接的一种新方法:
还是将地图按照标准尺寸均匀切割成静态的网格,每个格子由一个具体的Node负责,但是根据负载情况,能够实时的迁移到其他 Node上。在迁移分为三个阶段:准备,切换,完成。三个状态由Node Master负责维护。准备阶段新的 Node开始同步老 Node上面该网格的数据,完成后告诉NM;NM确认OK后同时通知新旧 Node完成切换。完成切换后,如果 Obj服务器还在和老的 Node进行通信,老的 Node将会对它进行纠正,得到纠正的 OBJ将修正自己的状态,和新的 Node进行通信。
很多无缝动态负载均衡的服务端宣称自己支持无限的人数,但不意味着 MMORPG游戏的人数上限真的可以无限扩充,因为这样的体系会受制于网络带宽和客户端性能。带宽决定了同一个区域最大广播上限,而客户端性能决定了同一个屏幕到底可以绘制多少个角色。
从无缝地图引入了分布式对象模型开始,已经完全脱离 MUDOS体系,成为一种新的服务端模型。又由于动态负载均衡的引入,让无缝服务器如虎添翼,容纳着超过上一代游戏服务器数倍的人数上限,并提供了更好的游戏体验,我们称其为第三代游戏服务端架构。网游以大型多人角色扮演为开端,RPG网游在相当长的时间里一度占据90%以上,使得基于 MMORPG的服务端架构得到了蓬勃的发展,然而随着玩家对RPG的疲惫,各种非MMORPG 游戏如雨后春笋般的出现在人们眼前,受到市场的欢迎。
类型5:战网游戏服务器
经典战网服务端和 RPG游戏有两个区别:RPG是分区分服的,北京区的用户和广州区的用户老死不相往来。而战网,虽然每局游戏一般都是 8人以内,但全国只有一套服务器,所有的玩家都可以在一起游戏,而玩家和玩家之使用 P2P的方式连接在一起,组成一局游戏:
玩家通过 Match Making 服务器使用:创建、加入、自动匹配、邀请 等方式组成一局游戏。服务器会选择一个人做 Host,其他人 P2P连接到做主的玩家上来。STUN是帮助玩家之间建立 P2P的牵引服务器,而由于 P2P联通情况大概只有 75%,实在联不通的玩家会通过 Forward进行转发。
大量的连接对战,体育竞技游戏采用类似的结构。P2P有网状模型(所有玩家互相连接),和星状模型(所有玩家连接一个主玩家)。复杂的游戏状态在网状模型下难以形成一致,因此星状P2P模型经受住了历史的考验。除去游戏数据,支持语音的战网系统也会将所有人的语音数据发送到做主的那个玩家机器上,通过混音去重再编码的方式返回给所有用户。
战网类游戏,以竞技、体育、动作等类型的游戏为主,较慢节奏的 RPG(包括ARPG)有本质上的区别,而激烈的游戏过程必然带来到较 RPG复杂的多的同步策略,这样的同步机制往往带来的是很多游戏结果由客户端直接计算得出,那在到处都是破解的今天,如何保证游戏结果的公正呢?
主要方法就是投票法,所有客户端都会独立计算,然后传递给服务器。如果结果相同就更新记录,如果结果不一致,会采取类似投票的方式确定最终结果。同时记录本剧游戏的所有输入,在可能的情况下,找另外闲散的游戏客户端验算整局游戏是否为该结果。并且记录经常有作弊嫌疑的用户,供运营人员封号时参考。
类型6:休闲游戏服务器
休闲游戏同战网服务器类似,都是全区架构,不同的是有房间服务器,还有具体的游戏服务器,游戏主体不再以玩家 P2P进行,而是连接到专门的游戏服务器处理:
和战网一样的全区架构,用户数据不能象分区的 RPG那样一次性load到内存,然后在内存里面直接修改。全区架构下,为了应对一个用户同时玩几个游戏,用户数据需要区分基本数据和不同的游戏数据,而游戏数据又需要区分积分数据、和文档数据。胜平负之类的积分可以直接提交增量修改,而更为普遍的文档类数据则需要提供读写令牌,写令牌只有一块,读令牌有很多块。同帐号同一个游戏同时在两台电脑上玩时,最先开始的那个游戏获得写令牌,可以操作任意的用户数据。而后开始的那个游戏除了可以提交胜平负积分的增量改变外,对用户数据采用只读的方式,保证游戏能运行下去,但是会提示用户,游戏数据锁定。
类型7:现代动作网游
从早期的韩国动作游戏开始,传统的战网动作类游戏和 RPG游戏开始尝试融合。单纯的动作游戏玩家容易疲倦,留存也没有 RPG那么高;而单纯 RPG战斗却又慢节奏的乏味,无法满足很多玩家激烈对抗的期望,于是二者开始融合成为新一代的:动作 + 城镇 模式。玩家在城镇中聚集,然后以开副本的方式几个人出去以动作游戏的玩法来完成各种 RPG任务。本质就是一套 RPG服务端+副本服务端。由于每次副本时人物可以控制在8人以内,因此可以获得更为实时的游戏体验,让玩家玩的更加爽快。
说了那么多的游戏服务器类型,其实也差不多了,剩下的类型大家拼凑一下其实也就是这个样子而已。游戏服务端经历了那么多结构上的变迁,内部开发模式是否依然不变?究竟是继续延续传统的开发方式?还是有了更多突破性的方法?经历那么多次架构变迁,后面是否有共通的逻辑?未来的发展还会存在哪些困难?游戏服务端开发如何达到最终的彼岸?请看下节:技术的演进。
网络游戏同步法则
网路的硬件也有限,而人的创造也无限,在公网平均130ms的Latency下,是不存在”完全的”的同步情况。如何通过消除/隐藏延时,将用户带入快速的交互式实时游戏中,体验完美的互动娱乐呢?
以下六点,将助你分清楚哪些我们可以努力,哪些我们不值得努力,弄明白实时游戏中同步问题关键之所在,巧妙的化解与规避游戏,最终在适合普遍用户网络环境中(200ms),实现实时快速互动游戏:
1. 基本情况:
- (A) 网络性能指标一:带宽,限制了实时游戏的人数容量
- (B) 网络性能指标二:延时,决定了实时游戏的最低反应时间
2. 两个基本原则:
- (A) 让所有的用户屏幕上面表现出完全不同的表象是完全没有问题的。
- (B) 把这些完全不同表象完全柔和在一个统一的逻辑中也是完全没有问题的。
3. 同步的十二条应对策略:
- (A) 最大可能减少游戏中的数据传输
- (B) 将阻塞通信放到线程池中实现
- (C) 永远不要为了等待某个数据而不让游戏进行下去
- (D) 利用预测和插值改进游戏的效果
- (E) 当使用预测插值的时候传送的数据不仅包括坐标,还需要速度和加速度
- (F) 将输入数据枷锁或者队列化(例如键盘消息队列),直到下次发送数据的时刻,传统的方法是在固定的时间(发送数据前)检测键盘,在游戏的原理上隐藏延时
- (G) 使用事件调度表,将需要在所有用户客户端同时发生的事件,提前广播到所有用户
- (H) 使用多次攻击来杀死一个精灵,尽量减少一次性的、确定性的、延时敏感的事件
- (I) 延长子弹或者火箭在空中飞行的时间(在其飞行的同时,在所有客户端进行预测插值)
- (J) 所有物体从一个地方移动到另外一个地方都需要时间,避免诸如”瞬间移动”的设计
- (K) 尽量使游戏中所有精灵,飞船或者其他物体,都按照可预测的轨迹运行,比如在移动中增加惯性
- (L) 充分发挥创造力,尽最大可能的合并游戏中前后相关的事件,合并游戏中存在的延时此问题,需要在技术上改进的同时也需要策划有所重视,规避一些影响较大的设计,巧妙的隐藏”延时”
4. 同步问题现状:
- (A) 重视程度不够:很多人尚未意识到此问题的存在,曾有公司花半年时间打算做一款”松鼠大战”的网络版。
- (B) 技术上无彻底解决方案:对于多数程序员,单机游戏技术善未成熟就匆匆步入网络时代。
- (C) 研究这个技术需要条件:需要有实力的公司才能提供,无此条件,即便有能力的程序员也无法成功。
5. 目前网游的三大技术难题:
- (A) 服务器的响应问题:如何使服务器在支持越来越多的人数的情况下提供最高的响应。
- (B) 同步问题:如何在有限的网络响应情况下,实现快速实时类游戏,提供最完美的交互。
- (C) 服务器分布式问题:如何在统一用户数据的情况下,利用分部式将各个分散的”世界”统一到一个”世界”中。
谁能真正解决好以上三个问题,配合策划在设计上的突破,将使其他人在至少两年内无法超越。
6. 相关补充:
- (A) 网格技术现在还是抄作,真正用到游戏中,还有很多技术难点需要突破(比如:目前网格的单位计算时间是以秒计算).
- (B) 其实与很多人想法相反的是现在3D技术早已不是主要的矛盾。而现在国内外对于以上三个问题可以说处于同一个起跑线上,完全有机会取得先机。
- (C) 现在解决同步问题已经很紧迫,而同时所需要的环境也已经成熟,只要有所关注,半年之内可以得出较成熟的结论
那么具体怎么解决呢?再下一步怎么办?
这就得自己去实践了,我只说这么多了,哈哈,不然又教懒了那些成天再网上搜方案的人。
问题汇总
域名状态 注册局设置暂停解析(serverHold)
处于该状态域名解析暂停,需联系注册局解除该状态(.cn国内中英文域名注册成功后未通过实名审核时多出现该种状态,需在域名有效期内完成实名审核后解除)
1. 关于域名到期删除规则实施的解释:
国际域名:
- (1) 到期当天暂停解析,如果在72小时未续费,则修改域名DNS指向广告页面(停放)。域名到期后30-45天为域名保留期(不同注册商政策规定时间不同)
- (2) 过了保留期域名将进入赎回期(REDEMPTIONPERIOD,为期30天)
- (3) 过了赎回期域名将进入为期5天左右的删除期,删除期过后域名开放,任何人可注。
2. 关于域名状态的解释:
cn域名各个状态说明:
- 以client开头的状态表示由客户端(注册商)可以增加的状态
- 以server开头的状态表示服务器端(CNNIC)操作增加的状态
- 既不以client开头也不以server开头的状态由服务器端管理
域名的状态解释:
- ok 正常状态
- inactive 非激活状态(注册的时候没有填写域名服务器,不能进行解析)
- clientDeleteProhibited 禁止删除
- serverDeleteProhibited 禁止删除
- clientUpdateProhibited 禁止修改
- serverUpdateProhibited 禁止修改
- pendingDelete 正在删除过程中
- pendingTransfer 正在转移过程中
- clientTransferProhibited 禁止转移
- serverTransferProhibited 禁止转移
- clientRenewProhibited 禁止续费
- serverRenewProhibited 禁止续费
- clientHold 停止解析
- serverHold 停止解析
- pendingVerification 注册信息正在确认过程中
强联网对战客户端开发注意事项
1. 所有关于游戏数值更改的方法,都要以游戏主循环帧为标准
比如攻击扣血判定,不能以动画帧的回调来触发,要以用户攻击操作后,就马上在客户端判定是否击中,以及扣多少血,在第几帧后扣血等数据,连同用户攻击操作,都一起发送给服务器端
2. 所有根据是否是敌我进行分别计算的地方,一定要注意,某些方法必须不论敌我都要调用!!!!!!!!
比如地图移动,如果是自己,则移动地图,不移动主角位置;如果是敌人,则不移动地图(但一定要调用移动地图的方法,只是移动的位置变量为0),移动主角位置
网络通用问题
1. DNS_PROBE_POSSIBLE
在运行中输入ipconfig /flushdns 重建本地DNS缓存即可