我想做一款小网络游戏,服务器端和客户端 服务器端必须是同时进行编写还是可以

中国领先的IT技术网站
51CTO旗下网站
游戏服务器:到底使用UDP还是TCP
一般来说你会听到人们这样说:“除非你正在写一个动作类游戏,否则你就用TCP吧” 或者是 “你能够在MMO游戏中用TCP,因为魔兽世界就用的TCP!”
作者:菜鸟浮出水 编译来源:伯乐在线| 09:51
在编写网络游戏的时候,到底使用UDP还是TCP的问题迟早都要面对。
一般来说你会听到人们这样说:&除非你正在写一个动作类游戏,否则你就用TCP吧& 或者是 &你能够在MMO游戏中用TCP,因为魔兽世界就用的TCP!&
遗憾的是,这些观点都没有反映这个问题的复杂性。
首先,说明一下,我之前主要是用TCP进行网络编程。我曾为一个流行的在线纸牌游戏编写服务器了好几年,在高峰期我们的每台服务器能够承受个连接(同一台物理机器上有多个服务器进程在跑)都没有问题。在我来看,TCP是一种安全而且常见的选择。
尽管如此,我们最新的项目却是使用UDP协议,而且我们的项目无法通过任何方式在TCP下工作。事实上,项目一开始使用的TCP,但是后来发现我们使用TCP无法达到我们需求的连接数量时,我们只能换成UDP了。
在使用中TCP表现怎么样呢
从原理上,TCP的优势有:
简单直接的长连接
可靠的信息传输
数据包的大小没有限制
任何一个和TCP打过交道的人都知道,要实现一个稳定的TCP网络连接,需要处理各种隐藏的坑,比如断线检测、慢速客户端响应阻塞数据包,对开放连接的各种dos攻击,阻塞和非阻塞IO模型等等。
除了上面列出的这些问题外,一个好的TCP模块确实不好编码实现。
但是,TCP最糟糕的特性是它对阻塞的控制。一般来说,TCP假定丢包是由于网络带宽不够造成的,所以发生这种情况的时候,TCP就会减少发包速度。
在3G或WiFi下,一个数据包丢失了,你希望的是立马重发这个数据包,然而TCP的阻塞机制却完全是采用相反的方式来处理!
而且没有任何办法能够绕过这个机制,因为这是TCP协议构建的基础。这就是为什么在3G或者WiFi环境下,ping值能够上升到1000多毫秒的原因。
为什么不用UDP
UDP相对TCP来说既简单又困难。
举个例子来说,UDP是基于数据包构建,这意味着在某些方面需要你完全颠覆在TCP下的观念。UDP只使用一个socket进行通信,不像TCP需要为每一个客户端建立一个socket连接。这些都是UDP非常不错的地方。
但是,大多数情况下你需要的仅仅是一些连接的概念罢了,一些基本的包序功能,以及所谓的连接可靠性。可惜的是,这些功能UDP都没有办法简单的提供给你,而你使用TCP却都可以免费得到。
这也是人们为什么经常推荐TCP的原因。在用TCP的时候你可以不考虑这些问题,直到你需要同步连接的数量级达到500以上的时候。
所以,是的,UDP没有提供所有的解决方法,但是就像你看到的那样,这也正是UDP好用的地方。在某种意义上来说,TCP对UDP就好比是Hibernate和手写SQL的区别。
使用TCP失败的地方
人们经常给你建议,让你去使用TCP,比如&TCP跟UDP一样快&或者&游戏X用TCP如此成功,所以TCP当然是首选&,然而,他们完全没有理解为什么在那个特定的游戏中TCP是有效的,为什么UDP不按照顺序发送数据包呢?
那么为什么魔兽世界采用TCP呢?首先我们需要解释这个问题。这个问题其实是&为什么魔兽世界有的时候1000毫秒以上的延迟还能够运行?&这是TCP的性质决定的,在发生丢包的时候,会产生巨大的延迟,因为TCP首先会去检测哪些包发生了丢失,然后重发所有丢失的包,直到他们都被接收到。
可靠的UDP也是有延迟的,但是由于它是在UDP的基础之上建立的通信协议,所以可以通过多种方式来减少延迟,不像TCP,所有的东西都要依赖于TCP协议本身而无法被更改。
就这一点来讲,一些人要开始提到Nagle算法了,实际上它是你在实现任意一个对延迟敏感的TCP模型时首先需要禁止使用的。
那么魔兽世界以及其他的一些游戏是怎么处理延迟问题的呢?
方法也很简单,他们能够隐藏掉延迟带来的影响。
在魔兽世界中,玩家和玩家是无法碰撞的:因为这类碰撞是无法通过一些预测来处理的,但是玩家和环境之间的碰撞却是可以通过预测来处理的,所以这里使用TCP是没有问题的。
我们来看一下魔兽世界的战斗就会发现,玩家的攻击指令发送给服务器的操作是放在比如&attack_entity(entity_id)&或者&cast_spell(entity_id, spell_id)&的接口中来做的,换句话说,瞄准操作是独立于进行的。如此一来,一些类似发起攻击动作和释放技能特效就能够在没有收到服务器确认的情况下就直接执行,比如展现冰冻技能的效果就可以在服务器没有返回数据前在客户端就做出来。
客户端直接开始进行计算而不等待服务端确认是一种典型的隐藏延迟的技术。
几年前,我为一个叫&Five Card Jazz&的纸牌游戏编写过客户端。它使用的是http协议,它比直接的TCP协议连接的延迟更加严重。
我们用简单的纸牌绘制和抽牌的动画来掩盖延迟的问题,所以延迟的问题只在非常糟糕的连接下才会被看出来。这种方法也非常的典型:发送请求的同时开始播放牌桌的动画,一直播放翻动最后一张牌直到接收到了服务端传回来的数据为止。魔兽世界的战斗特效就是使用类似的原理。
这也意味着,我们到底是使用TCP还是UDP取决于我们能否隐藏延迟。
TCP在什么时候失效
一个采用TCP的游戏必须能够处理好突发的延迟问题(纸牌客户端就很典型,对突发性的一秒的延迟,玩家也不会产生什么抱怨)或者是拥有缓解延迟问题的好方法。
但是如果你运行的是一个无法使用任何减缓延迟措施的游戏呢?玩家对玩家的动作类游戏通常就属于这个范畴,但是这也不仅仅限于动作类游戏。
举个例子:
我目前正在写一个多人游戏(War Arcana)。
一种常见的操作是,你快速的移动你的角色通过一张充满战争迷雾的世界地图,但是一旦你探索过,迷雾就会被打开。
为了确保游戏的规则,防止玩家作弊,服务器只能显示玩家当前位置附近的信息。这意味着不像魔兽世界,玩家无法在没有得到服务器响应的情况下,做出完整的动作。和Five Card Jazz相比,我们即使允许500毫秒的延迟,也已经非常困难了。
在实现了游戏的原型后,在局域网内一切都进行的非常顺利,但当我们在WiFi环境下测试时,操作会间歇性的卡起来或者延迟高起来。写了一些测试程序之后发现,WiFi环境下偶尔会发生丢包行为,每当发生丢包的时候,服务器的响应速度就从100-150毫秒上升到毫秒。
没有任何办法可以绕过TCP的这个设置来避开这个问题。
我们替换了TCP的代码,用了自定义的可靠的UDP来实现,把大量的丢包产生的延迟降到了仅仅只有50毫秒,甚至比以前TCP不丢包的情况一个来回的延迟还要小。当然,这只可能建立在UDP之上,这样我们才对可靠性拥有完全的掌控力。
困惑:可靠的UDP只是TCP的一种简单的实现?
你有没有听过这种说法:&可靠的UDP就像TCP一样,所以还是用TCP吧&。
问题是这种说法是错误的。可靠的UDP一点也不像TCP,要去实现一个特殊的阻塞控制。事实上,这也是你使用可靠UDP代替TCP的最大的原因,避免TCP的阻塞控制。
另一个重点是可靠的UDP的可靠性是如何保证的。这里有很多种方法去实现。我非常喜欢Quake3网络库代码里的一些想法,它们也激发了我在War Arcana中使用UDP协议。
你也可以使用许多支持可靠通信的UDP库,当然,这样在可靠性方面,相比自己手动实现全部的代码而言,可能会更加通用而失去了一些性能优势。
那么到底是用UDP还是TCP呢?
如果是由客户端间歇性的发起无状态的查询,并且偶尔发生延迟是可以容忍,那么使用HTTP/HTTPS吧。
如果客户端和服务器都可以独立发包,但是偶尔发生延迟可以容忍(比如:在线的纸牌游戏,许多MMO类的游戏),那么使用TCP长连接吧。
如果客户端和服务器都可以独立发包,而且无法忍受延迟(比如:大多数的多人动作类游戏,一些MMO类游戏),那么使用UDP吧。
这些也应该考虑在内:你的MMO客户端也许首先使用HTTP去获取上一次的更新内容,然后使用UDP跟游戏服务器进行连接。
永远不要害怕去使用最佳的工具来解决问题。
原文链接:&&&&翻译:&-&
译文链接:&
【编辑推荐】
【责任编辑: TEL:(010)】
大家都在看猜你喜欢
头条头条头条原创头条
24H热文一周话题本月最赞
讲师:1人学习过
讲师:33人学习过
讲师:0人学习过
精选博文论坛热帖下载排行
主要内容:
● 如何设计像自动售货机那样有效的用户界面。
● 深入理解窗口和对话框的管理机制。
● 为什么性能优化与我们在直觉上的理...
订阅51CTO邮刊主题信息(必填)
主题描述(最多限制在50个字符)
申请人信息(必填)
申请信息已提交审核,请注意查收邮件,我们会尽快给您反馈。
如有疑问,请联系
CSDN &《程序员》编辑/记者,投稿&纠错等事宜请致邮
傻丫头和高科技产物小心翼翼的初恋
如今的编程是一场程序员和上帝的竞赛,程序员要开发出更大更好、傻瓜都会用到软件。而上帝在努力创造出更大更傻的傻瓜。目前为止,上帝是赢的。个人网站:。个人QQ群:、
个人大数据技术博客:
Sergey Ignatchenko针对大规模多人网络游戏所撰写的开发与部署一书章章精彩,不过里面的内容远不仅适用于游戏。下面是这本书的:“关于服务器端架构、前端服务器与客户端随机负载平衡”问题
在前端服务器输入
[Enter Juliet]
Thou art as sweet as the sum of the sum of Romeo and his horse and his black cat! Speak thy mind!
[Exit Juliet]
这是用莎士比亚编程语言所编写的一段程序样例。我们的经典部署架构(尤其在使用FSM时)并不差,运行良好,但对于大多游戏来说仍有很大的改进空间。更具体来讲,我们可以在游戏服务器前面再另加一行服务器,如下图:
如图所示,与经典部署架构相比,只是在游戏服务器前面增加了一行前端服务器。这些新增的前端服务器旨在处理所有来自客户端的通讯:所有“那个玩家有没有连接上”这种讨厌的问题,所有客户端到服务器的数据加密(如适用),所有这些密码等等,所有相对奇怪的reliable-UDP协议(如适用),当然还有客户端与不同游戏服务器之间的路径选择信息——这里处理着所有客户端的通讯。此外,一般来说这些前端服务器在需要时会存储相关游戏世界的副本,并在游戏世界升级时扮演集中器(concentrator)的角色。也就是说:即便游戏服务器上有10万人同时观看某个比赛(比如某个比赛的决赛之类),只需要将更新发给几个前端服务器,而前端服务器会解决10万人的数据分配问题。在大型决赛中,当有成千上万人想要观看比赛时,这种能力非常方便。实际上无需将其制成直播视频——对现有的玩家来说并不方便,也贵得要命,但在客户端里是可以实现的。我们稍候会对前端服务器的实现进行一些讨论,不过现在先讨论最重要的部分:前端服务器必须能够在不对玩家造成重大不便的情况下就能简单更换。也就是说,如果任何前端服务器因某种原因发生故障,玩家最多能看到几秒钟的断开。虽然仍会造成混乱,但比这样的场景——“整个游戏世界宕机,我们需要从备份恢复”好多了。换句话说,无论何时前端服务器因某种原因而崩溃(更糟的情况下甚至产生“黑洞”),所有连接的客户端都会检测到这一情况,并自动重连到其他前端服务器上;在这种情况下,所有玩家能看到的只是瞬间断开(也很讨厌,但比游戏挂掉好太多)。前端服务器:好处每当我们增加复杂层级的同时,总会产生这样一个问题“我们真的需要它吗”?从我看到的情况,将可轻松替换的前端服务器放置在游戏服务器的前面很有价值,有着诸多好处。具体来说:
前端服务器从游戏服务器上分担了一些负载,并且很容易替换。
也就是说游戏服务器的数量可以减少了。
再加上前端服务器容易替换的特性,站点整体的可靠性得以增加;部分游戏世界服务器宕机的情况将越来越少。
前端服务器可以使用更廉价的那些,严格说来甚至无需使用ECC和RAID,但游戏服务器当然得使用这种的。如上所述,前端服务器很容易替换,因此如果发生故障,其复杂可以自动重新分配到其他前端服务器上。如果想要部署在云端,你可能会想给前端服务器考虑更廉价的配置(即便是来自不同的CSP[1])。
允许客户端有指向全站的唯一连接;这样做的好处包括:更好地控制与玩家终端的连接,从而可以控制来自不同数据流的优先级,消除那些难以分析的“部分连接”,并更好地隐藏站点的实现细节。
允许简单的客户端负载平衡(无需硬件负载均衡器等等)。想要了解更多信息,参见下面的“客户端负载均衡与大数字法则”部分。
在前端服务器上保存相关游戏世界的副本使得想要观看站点上某些比赛(比如大决赛之类的[2])的用户数量近乎无限;最重要的是,还不影响游戏服务器的性能! 此外通常无需为决赛组织任何工作,只要正确构建,系统便会按以下方式自主处理:
每当有人来观看某场游戏,客户端会向前端服务器发出请求;
如果前端服务器没有所请求游戏的副本,就会向相关的游戏服务器发出请求,同时更新到游戏世界状态;
从这时起,前端服务器会保持游戏世界的副本“同步”,向所有请求的客户端发送该副本(与更新内容);
也就是说从这时起,即便有10万个用户在观看该游戏服务器上的某场游戏,所有的额外负载都由前端服务器处理,不会影响到游戏服务器;
前端服务器允许稍后增加安全性(基本能够担任DMZ的角色)。
注1:要记得你仍需保持一流的连通性;
注2:由于大决赛是吸引注意力很好的方式,这种做法明显能够提供竞争力。前端服务器:服务器延迟与玩家间延迟的差异关于设置前端服务器的负面影响,我只能想到这两个。第一个是由前端服务器所带来的额外延迟。具体来讲,我们是在讨论从应用层面的客户端所发送的数据包,经前端服务器处理,进入前端服务器的TCP协议栈[3],再离开游戏服务器的TCP协议栈,到达游戏服务器应用层面所需的时间(再加上反向重复这一步骤所需的时间)。让我们查看一下这个额外生成的延迟。从我的经验来讲,如果所使用的通讯层数据库相当优秀,应用层面的前端服务器的处理时间会在几微秒[4]。然后从前端服务器到游戏服务器会建立点对点TCP连接,这类连接的延迟(通过10G以太网)在8us左右[Larsen2007]。将这两个延迟的时间加在一起,并乘以2得到往返延时(RTT),这个数字显示延时仍远远低于100us。然而,还有进一步的原因(诸如延时开关,不同OS之间的差异,游戏之间的差异等)让人无法确认延时会低于100us——也就是说,也许能达到,也许不能。另一方面,如果实现时足够仔细,前端服务器所带来的延迟会相应减少,在大多情况下可减少到1ms。总结:
如果可以忍受1毫秒左右的额外延迟,就别再担心额外延迟的问题了,启用前端服务器吧;包括所有类型的游戏,唯一可能的例外是大型多人在线射击游戏(MMOFPS)。
如果可以忍受的延迟远低于1毫秒的话(难以想象,因为这个数字比1/60秒/帧的更新时间还要低一个数量级,不过在MMOFPS世界里什么都有可能)——就要多考虑一下了,尝试在实践中找出延迟种类;如果经过实验,确实无法接受所带来的延时[5],就可能需要放弃前端服务器了。
具体情况可能有所不同,无法作出保证,不包括电池。
如果某些前端服务器过载,或运行的硬件差异很大,那么使用前端服务器的第二个(依我来看相当理论化,不过一般要看具体情况)潜在问题就会产生,那些连接到负载较低的前端服务器的玩家延迟也较低,从而在比赛中更有优势。一方面来说,在真实世界的部署中,我并未发现有实际区别——也就是说,按我的经验如果某些前端服务器过载,这意味着大多数已经达到90%以上的负荷,这是应当避免的。另一方面,无论根据实际情况还是理论上,你都可能受到这种影响,尽管除了MMOFPS我尚未看到现实案例。如果这类玩家之间的延迟差异成为案例(只有成为真正的问题时),对于与某些前端服务器相连接的某些游戏世界的玩家来说,可能需要实现某类关联(更多相关信息请参见“关联”部分)。然而,大规模关联可能会抵消前端服务器所带来的多数好处,因此如果需要在所有游戏中执行关联的话,不架设前端服务器也许更好一些;只在游戏的一小部分中实现关联(比如“高规格比赛”)带来的麻烦会更少一些,请查看后面的“关联”部分)。注3:没错,在多数情况下我主张在服务器间通讯中使用TCP连接,参见上面的“服务器间通讯”章节。另一方面,UDP也是可以使用的。
注4:注意这可能成为非普通情况,此外我已经实现了。
注5:理论上你可能还想实验一下无线带宽技术什么的——完全适用于FSM架构,因为其中的通讯与其他部分的代码相分离,但一般来说不值得尝试。客户端随机平衡与大数字法则一旦设置了前端服务器,就会产生问题“如何确保所有前端服务器保持同等负载”,即典型的负载均衡问题。一般来讲,在至少近20年间负载均衡都是个很大的话题。从中引申出的三种常见技术分别是:DNS负载均衡、客户端随机平衡还有服务器端(一般是基于硬件的)负载均衡。而针对最后一种,随着制造硬件设备技术改进,毫无疑问至少在企业中普及度正在增长。我们来深入探究一下这些负载均衡的解决方案。DNS负载均衡DNS负载均衡是基于传统DNS请求的。每当客户端请求站点前端地址(以IP地址显示)时,就会向相应的DNS服务器发送请求。如果按DNS负载均衡来配置DNS服务器的话,就会向不同的DNS请求来源返回不同的IP地址,因按循环轮流(round-robin)的模式[6]而得名。在不同的web服务器使用均衡浏览器时,DNS负载均衡有两个主要的缺点。首先将DNS服务器与请求路径缓存在一起会有问题(这是DNS处理的标准做法)。也就是说,即便服务器按均衡模式忠实返回所有的IP,其中一个返回的IP可能被Big Fat DNS服务器缓存(试想Comcast或AT&T),并分配给成千上万个客户端;在这种情况下,被缓存的“幸运”IP可能获得倾斜。对网络服务器使用DNS均衡负载的第二个问题在于,如果其中一个服务器宕机,普通的网络浏览器不会尝试列表中的其他服务器,因此一般来讲网络服务器范围中的均衡DNS无法提供服务器容错功能。幸运地是,因为我们有客户端,可以同时轻松地解决这两个问题。此外这些技术也对你基于浏览器的游戏奏效(在JS加载完毕并开始运行后)。注6:严格来讲要稍微复杂一些,因为DNS数据包中含有服务器列表,但由于近乎所有人都会忽略所返回的数据包中其他条目,只关注了第一条,因此与每条请求只返回一个IP相差无几。例外情况就是服务器可以自行作出选择,参见“客户端均衡”。客户端随机平衡为了改进DNS均衡,人们使用了一个简单的办法。我们不再从服务器端所有内容中循环返回,而是直接将同一张服务器列表返回给所有客户端。这张列表可能会硬编码到客户端中(我个人就用了这个办法,大获成功),或者通过DNS来分配这张列表,但要按照简单的IP列表匹配相应名称的形式,用getaddrinfo() 或类似命令来检索。用哪个办法无关紧要。一旦客户端获得IP列表,一切都变得非常简单。客户端从IP列表中随机获取,尝试连接随机选中的IP。如果连接不成功或者丢失,客户端再尝试另一个随机IP,尝试重连。失败案例包括:
int myrand() {//DON'T DO THIS!
srand(time(0));
return rand();
在这种情况下,如果出现大量连接断开的情况,结果导致玩家尝试同时重连,IP分配很可能由于想要获得IP地址的各个客户端之间差异过小而被倾斜;如果所有客户端都在5秒内尝试重连,用myrand()函数最多只能获得5个不同的IP,甚至更少。除了这种极端情况,任何随机数发生器都是适用的。甚至简单的线性同余发生器,在项目启动时按time(0)(并非像上面列子中的那样在请求时)在实践中都是可用的。大数字法则——根据这一法则,从大量实验中所获得的结果平均值应当很接近期望的数值,并随着实验数量增加而越来越接近。客户端随机平衡:大数字法则,对比DNS负载均衡与DNS负载均衡不同(这一方式在理论上提供了“理想的”平衡),客户端随机均衡依赖大数字统计法则来获得服务器间客户端的平衡分配。这一法则基本上就是在说,对于单个测试,实验数量越多,分配越平衡。TODO:增加二项分布的内容,还有一个案例。事实上,尽管在理论上并不理想,客户端随机平衡比DNS负载均衡更为平衡。有双重原因。第一,一旦客户端数字很大(成百甚至更多),客户端随机平衡就会足够平衡——如果系统有成千上万名玩家,只有很少一些会出现问题;不会达到非常平衡,不过这种不平衡影响不大,而且随着人数增长,还会更加平衡。但是,从积极方面来讲,客户端随机平衡不会出现DNS缓存问题。即便使用DNS来分配IP列表,而且该列表被缓存,在使用客户端随机平衡时,在设计上系统中循环的所有IP都是一致的,因此与DNS负载均衡不同,缓存完全不会改变客户端分配。总结:使用DNS负载均衡时要格外小心。换句话说,客户端随机平衡对于游戏效果更好,因为同时在线的玩家数量会从几百增长到成千上万;运行起来几乎没有问题,始终提供几乎完美的平衡体验。也就是说,如果整体的平衡负载是50%,事实上有些是48%,而有些是52%,但不会超过这个数字。[7]至于刚刚在说DNS负载均衡运用到web浏览器时的第二个缺点(大多数的浏览器无法提供容错,来避免某个服务器崩溃)——一旦我们在客户端有整张列表时,这个问题就烟消云散了。一旦发现连接失败,可以另选一个IP地址。注7:当然,服务器必须得运行足够的时间才能得出相同的结果;如果一个服务器刚刚加入服务,必须得过上一定的时间才能达到与其他服务器相同的负载级别。如果必要的话,也可以抑制这种影响。尽管抑制会造成不利影响,而且实践中从未见过此类需求。服务器端负载均衡这种做法与DNS负载均衡和客户端随机平衡都不相同,用到了服务器端负载均衡器。负载均衡器一般是额外的设备,设在服务器前面,完成负载均衡的工作。额外的均衡能力一般对于游戏完全没有必要(大数字法则的趋势非常鲜明)。在不同客户端造负载不同的情况下,服务器端负载均衡确实有更强大的平衡能力(因此服务器端均衡器可以在大数字法则无效的情况下继续使用)。但是一方面来说,这些额外的平衡能力对于游戏来说一般完全没有必要,大数字法则效果良好。另一方面,这种负载均衡器设备昂贵的要命(如果想要冗余的话价格还得翻倍,而冗余当然是必要的),还不包括数据中心间的平衡和容错(需要设计),并带来了额外的不可控延迟。[8]此外,在谈到冗余和设备花费时,不少硬件生产商都会说:“来用我们的均衡设备吧,使用主动/主动配置,不会造成浪费。” 尽管的确是这样,可以使用很多主动/主动配置的服务器端负载均衡器,但还是得至少拥有一台冗余设备,在某台设备发生故障时处理相应负载。换句话说,如果你只有两台主动/主动配置设备,一台运行,每台设备的总负载必须低于50%,如果想要冗余这是难以避免的。综上,对于游戏负载均衡来说,服务器端负载均衡设备实际用途不大。即便你使用了基于网络的部署架构(如上所述),也应当避免使用服务器端负载均衡器。注8:大多数负载均衡器是为了平衡网站而设计,低于100毫秒几乎没什么关系,因此在购买设备前,至少要确保进行讨论,并测量延时情况(根据你的负载类型)。 均衡总结根据我的经验,客户端随机平衡(针对前端服务器)运行良好,没必要改用其他的。DNS负载均衡几乎普遍不如客户端均衡,而基于硬件的服务器端均衡器太过复杂与昂贵,一般没必要在游戏环境中使用。如上所注,可能需要服务器端均衡器的例外情况在于使用基于Web的部署架构时。作为CDN(内容分发网络)的前端服务器使用前端服务器作为CDN是有可能的(甚至还能用来构建自己的CDN)。即便所有的游戏服务器都在单独的数据中心里运行,对于某些类型的游戏来说,使用不同数据中心的前端服务器可能也是不错的。如下图:
这点与经典CDN的原理十分相似:减少终端用户的延迟。另一方面,我们需要注意:与经典CDN不同,游戏类CDN的内容并不是静态的,因此通过更换更好的设备,而在延时方面获得改善也是有可能的,一般能提高几毫秒。 使用这样的配置架构还有其他原因:确保在主数据库停机时,仍能连接到网络(保持“某些连接”);在实践中,如果有像样的数据库,这种情况不应该发生。更确切来说,数据库会偶尔碰到1.5-2分钟的短暂故障(典型的边界网关协议收敛时间)。因此如果你在找借口,想要使用上图的优雅架构的话,而且你的客户端可以极快地探测到故障,并重定向到另一个数据中心,这种结构可能会对你的用户有一些影响。想要让这种类似CDN的多数据中心前端服务器配置实现良好的话,需要考虑这么几点:
数据中心之间必须连接良好(上图中的“某些连接”)。至少ISP间设备应当根据你的ISP来设置,以确保关键路径使用最佳数据流。
严格来讲,“某些连接”不一定需要基于互联网;通过“专用”帧中继之类就可以降低数据中心间的延迟几毫秒,但花费可能会达到每月数万。
由于使用前端服务器充当“集中器”,“某些连接”的流量可能比去往客户端的那些要低1到2个数量级。
应当解决二级数据中心连通性下降的问题(特别是在数据中心间的连通性下降时)。最简单的处理办法就是让主数据中心拥有足够的处理能力(流量智能和CPU智能),以处理所有客户端,不过一般花费不斐。作为替换选项,根据游戏类型,在出现故障时关闭一些活动也是可以的。
CDN类布置的底线前端服务器的CDN类布置可能减少某些用户几毫秒的延时,前提是数据中心间的连接良好。从而改善一定的延时。根据我的经验,由于延迟方面的改善并不太大,来往于游戏服务器的数据包仍需走完全程,因此这种做法价值并不大。不过还是要记得有这种可能性。举个例子,在没有足够带宽直接提供给客户端的情况下,如果根据法律需要将游戏服务器保存在陌生位置时会很方便(例如赌牌游戏)。 前端服务器+游戏服务器=类CDN 换句话说,如果真的担心延时,将游戏世界服务器放置在离玩家近一些的地方会更好一些(留下数据库服务器),如下图:
这里我们将与时间最关键的东西(一般是游戏世界服务器)迁移到终端用户旁,降低相应数据中心附近用户的延迟情况。维护这样的基础设备确实令人头疼,不过这种做法是可取的。因此如果真的担心延迟情况,可以按照这种方式来部署。提醒一句:如果这样做,会出现“区域服务器”——同样也有自己的问题:你需要确保该区域的客户端只与相关的前端服务器连通,数据中心间连通的安全性也会成为很大的问题,等等;不过还是可行的,但这种做法仅限于真正有此需要的时候。关于关联在某些情况下,你可以决定需要一种“关联”,以便某类用户(一般是在特定游戏世界的用户)连到特定的前端服务器。注意:在提到前端服务器的时候,“关联”的概念与在网络服务器的均衡器中所使用的经典概念(一般指的是“持久性”或“粘性”)并不相同。在网络世界中,持久性与粘性指的是从同一个服务器到同一个的客户端(以处理会话与各客户端缓存)。但对于前端服务器来说是不同的,一般指的是前端服务器到游戏世界的关联(对于玩家,或者对于玩家和观看者),而不是客户端到服务器的关联(参见“前端服务器:服务器间延迟与玩家间延迟的不同”部分)。技术上来说,实现前端服务器到游戏世界的关联并不难,但真正的问题在于部署关联之后。简单来说,只要使用关联的游戏世界数量不大,就会很顺利。换句话说,一旦有大量玩家使用关联规则进行连接,不同的前端服务器间想要达到合理的均衡就变得十分困难。在没有关联时,由于大数字法则均衡实现近乎完美;在引入关联规则后,就会导致分配平衡倾斜,关联所影响的玩家越多,距离理想分配状态就偏离地越远,因此在达到负载均衡的同时管理这些规则可能成为很大的挑战。底线:尽可能避免关联。前端服务器:实现 现在我们来讨论一下如何实现前端服务器的部署。如上所述,前端服务器的关键属性是,在发生故障时易于替换。为了实现这一点,必须确保在所有前端服务器上没有原始的游戏世界换句话说,前端服务器应当只有原始游戏世界状态的副本,而原始的游戏世界状态则保存在游戏服务器上。
如果使用普通的订阅/发布器(或状态复制器)之类的东西,也无需太过担心,但如果在前端服务器中引入了任何定制逻辑的话,要非常小心,因为易于替换属性可能会导致重要资料遗失。前端服务器:QnFSM实现 前端服务器的一个实现是在纯粹的Queues-and-FSM架构下实现,如下图:
这里在游戏服务器方面,TCP-与UDP-相关的线程,与在“QnFSM下实现游戏服务器架构”部分中的描述非常类似;由一个或多个路径选择&数据线程(每个路径选择&数据 FSM中至少有一个线程)负责分发所有的数据包,并缓存数据(比如“游戏世界”数据)。我们来深入讨论一下这些路径相关的FSM。路径选择&数据 FSM每个路径选择&数据 FSM都有自己要处理的数据(与更新)。例如,这样一个路径选择&数据 FSM可能包含一个游戏世界状态。另一个路径选择&数据 FSM可能处理点对点数据包的分发工作,而数据包是往返于玩家与游戏服务器之间的。路径选择&数据FSM所处理数据类型一般来说会有三种类型:
普通连接处理器:处理点对点通讯,包括玩家输入与服务器之间的连接;
普通发布/订阅处理器:缓存与处理普通但结构化的数据,比如可用游戏列表——如果允许玩家选择游戏的话;
特殊游戏世界处理器:如果需求功能普通处理器无法解决的话,缓存与处理游戏世界数据。在很多情况下,可以不需要特殊游戏世界处理器;但如果想要执行某类服务器端过滤的话(比如服务器端的战争迷雾),使用它可以避免将数据发送给不应看到的用户,这样就无法从服务器端破解移除战争迷雾了——在这样的情况下,特殊游戏世界处理器非常必要。
在同一个路径选择&数据线程中有不止一个路径选择&数据FSM也是可能的(并且通常可行的),由于线程数量很大,这样能够减少不必要的负载(以及不必要的线程切换)。如何将这些路径选择&数据FSM结合到特定线程中——很大程度上取决于游戏,不过一般来说,普通连接处理器速度很快,可以放在一个线程中。对于普通发布/订阅器以及特殊游戏世界处理器来说,分发到不同的线程中应当考虑到一般负荷与允许的延迟。经验法则(一般来说):每个线程的FSM越多,延迟越高,线程相关的开销越小;不幸的是,其他依赖大多要根据游戏来定。路径选择&数据Factory Thread 路径选择&数据Factory Thread的主要责任就是,根据来自TCP/UDP线程的请求,创建路径选择&数据线程(与相应FSM)。路径选择&数据FSM的典型生命周期如下:
一个TCP/UDP FSM需要发送某些消息(或者提供某些状态的同步);如果路径选择&数据FSM中没有数据,需要在导入消息到自身缓存时发现这一情况。
TCP/UDP FSM向路径选择&数据Factory Thread发送请求;
Factory FSM(在适当的路径选择&数据FSM中)创建路径选择&数据线程;
从适当的路径选择&数据FSM中发出消息,返回请求的TCP/UDP线程;
Factory FSM报告消息队列的ID:
TCP/UDP FSM向适当的队列发送消息(使用ID而非指针,允许确定“recording”或者“replay”)。
每当路径选择&数据 FSM并非必须时,TCP/UDP会向Factory FSM报告:
如果是最后一个需要该路径选择&数据 FSM的TCP/UDP FSM,那么Factory FSM可能引导适当的路径选择&数据线程撤销该路径选择&数据FSM。
在游戏服务器和客户端中的路径选择&数据FSM我得承认,个人很喜欢这些路径选择&数据FSM。因为喜爱,我不止将它们用在前端服务器上,还用在游戏服务器和客户端上,尽管严格来说不应该放在那里;在简化工作上,它们确实帮了我大忙,让所有通讯都统一起来。不过是否在游戏服务器上和客户端时选用它们,就是你的选择了。前端服务器总结总结一下:根据经验法则,前端服务器是个好东西。尤其是:
减轻了游戏服务器的负载;
让系统设备价格更低(因为前端服务器比较便宜);
也提高了整体系统的可靠性(因为前端服务器很容易更换);
促进了单一客户端的连接(一般来讲是好事);
促进客户端负载均衡;
出现大事件时,接待10万+的观看者变得很简单(事实上没有限制);
缺点仅限于额外延迟,而这种延迟一般都在亚毫秒级别;
一般对于游戏来说,客户端负载均衡是最好的选择:
唯一可能的例外是基于网络的部署架构——可能需要服务器端均衡器;
需要避免大规模关联;
CDN类分布是有可能的,但有雷区;
前端服务器可以通过QnFSM架构实现。 原文链接:(译者/Vera 责编/钱曙光)(责编/钱曙光,关注架构和算法领域,寻求报道或者投稿请发邮件,交流探讨可加微信qshuguang2008,备注姓名+公司+职位)「CSDN 高级架构师群」,内有诸多知名互联网公司的大牛架构师,欢迎架构师加微信qshuguang2008入群,备注姓名+公司+职位。

我要回帖

更多关于 网络游戏服务器端编程 的文章

 

随机推荐