基本体系与建议

基本体系与建议

服务端成长路线

游戏公司培养新人,强调“边学边用”“有产出再深入”的技术成长路线,一般是让新人从开发简单的业务功能开始,再逐步深入底层,最后独当一面

第一年:能做好功能,这个阶段能按时、按质、按量做好业务功能,刚进游戏公司参与项目开发,需要从简单的活动功能写起,逐渐过渡到能编写较为复杂的跨服功能和战斗功能。
第二年:能用好框架,公司开启一个新项目,一般不会从零开始,而是会拿一套现有的代码参考,根据需求做修改,这个阶段需要你能分析别人为什么这样设计,并能修改 底层功能,改善性能问题。
第三年:能重建系统,当旧框架落后于时代,历史遗留问题过多,又想开展新业务,开发不同类型的游戏时,就要从零开始设计了,需要你有重新搭建整套服务端系统的能力。

前言

提到现在的互联网好像都是在写 web 前后端的,因为大学期间学校的科班出身最多的人就是接触前后端 web 知识,但有极小的人去往了游戏行业,我也是阴差阳错进入了一家游戏公司做游戏服务器开发,虽然在找工作前我做好的准备如 C++服务器开发、linux 系统编程等,但是游戏后台开发和 web 不一样,代码太多太乱,看见代码就想重构,也没有严格的培养体系,甚至没有很好的文档来进行学习,都是在踩坑。

第一次读 C++游戏服务器开发的书籍,之前有阅读过通用的 C++服务端有用的书籍,《C++ Primer》、《APUE》、《C++服务器开发精髓》等等。但是毕业的第一份工作是做游戏服务器 C++开发,所以我不得不开始去专业领域钻研,学习领域相关知识,人生总是要进步的嘛,不学习不努力提高自己,还想做白日梦,那样真遇到机会也轮不到自己。

开始

先读第一本书《多人在线游戏架构实战,基于 C++分布式游戏编程》,为什么选这本书,“C++”、“分布式”、“游戏编程”。这本书是他妈的毛病多,重 0 教你重构代码,重构一次又一次,可能优秀的同一没记住竟记住了比较差的方案,直接去学习书中最后完善好写好的框架,无疑不是一个好方法,读一遍就像弄透澈不太可能,快速过几遍,一定可以有更好的体会。 游戏分类常见有 MMO(Massively Multiplayer Online)、MOBA(Multiplayer Online Battle Arena)、SLG(Simulation Game)、PRG(Role-Playing Game)、Rogue-Like 等等。

基本体系

游戏服务器开发和传统的 web 开发有本质的区别,游戏开发不像 web 那样有现成的框架如 MVC 架构,游戏开发往往为了尽快满足策划的需求,尽快实现功能让游戏跑起来,但是功能越来越多,老代码修改越来越频繁,游戏测试暴露 bug 时让人束手无策,所以好的架构设计很重要,好的架构代码很重要。

游戏架构体系通常包括

系统初始化

系统初始化是在没有客户端连接的时候,服务器启动时所需要做的工作。基本上就是配置文件的读取,初始化系统参数。

但是我们必须要考虑的是:

游戏逻辑

游戏逻辑是游戏的核心功能实现,也是整个游戏的服务中心,它被开发的好坏,直接决定了游戏服务器在运行中的性能。那在游戏逻辑的开发中我们要注意些什么呢? 游戏是一种网络交互比较强的业务,好的底层通信,可以最大化游戏的性能,增加单台服务器处理的同时在线人数,给游戏带来更好的体验,至少不容易出现因为网络层导致的数据交互卡顿的现象。

代码一定要分层

1、协议层

协议层,也叫前后台交互层,它主要负责与前台交互协议的解析和返回数据。在这一层基本上没有什么业务逻辑实现。与前台交互的数据都在这一层开始,也在这一层终止。接收到客户端的请求,在这一层把需要的参数解析出来,再把参数传到业务逻辑方法中,业务逻辑方法处理完后,把要返回给客户端的数据再返回到这一层,在这一层组织数据,返回给客户端,这样就可以把业务逻辑和网络层分离,业务逻辑只关心业务实现,而且也方便对业务逻辑进行单元测试。

2、业务逻辑层

业务逻辑层,这里处理真正的游戏逻辑,该计算价格计算价格,该通关的通关,该计时的计时。该保存数据的保存数据。但是这一层不直接操作缓存或数据库,只是处理游戏逻辑计算。因为业务逻辑层是整个游戏事件的处理核心,所以他的处理是否正确直接决定游戏的正确性。**所以这一层的代码要尽量使用面向对象的方法去实现。**不要出现重复代码或相似的功能进行复制粘贴,这样修改起来非常不方便,可能是修改了某一处,而忘记了修改另外同样的代码。还要考虑每个方法都是可测试的,一个方法的行数最好不要超过一百行。另外,可以多看看设计模式的书,它可以帮助我们设计出灵活,整洁的代码。

数据库系统

数据库是存储数据库的核心,但是游戏数据在存储到数据库的时候会经过网络和磁盘的 IO,它的访问速度相对于内存来说是很慢的。一般来说,每次访问数据库都要和数据库建立连接,访问完成之后,为了节省数据库的连接资源,要再把连接断开。

这样无形中又为服务器增加了开销,在大量的数据访问时,可能会更慢,而游戏又是要求低延时的,这时该怎么办呢?我们想到了数据库连接池,即把访问数据库的连接放到一个地方管理,用完我不断开,用的时候去那拿,用完再放回去。这样不用每次都建立新的连接了。

缓存系统

游戏中,客户端与服务器的交互是要求低延迟的,延迟越低,用户体验越好。像之前说过的一样,低延迟就是要求服务器处理业务尽量的快,客户端一个请求过来,要在最短的时间内响应结果,最低不得超过 500ms,因为加上来回的网络传输耗时,基本上就是 600ms-到 700ms 了,再长玩家就会觉得游戏卡了。

如果直接从数据库中取数据,处理完之后再存回数据库的话,这个性能是跟不上的。在服务器,数据在内存中处理是最快的,所以我们要把一部分常用的数据提前加载到内存中,比如说游戏数据配置表,经常登陆的玩家数据等。这样在处理业务时,就不用走数据库了,直接从内存中取就可以了,速度更快。修改数据时写是直接写内存,然后标记为脏数据,用一些方法进行同步到数据库。

游戏中常见的缓存有:

1、直接将数据存储在服务器内存中
2、用第三方缓存工具,如 redis

游戏日志

一个游戏中更不能少了日志,而且日志一定要记录的详细,它是玩家在整个游戏中的行为记录,有了这个记录,我们就可以分析玩家的行为,查找游戏的不足,在处理玩家在游戏中的问题时,日志也是一个良好的凭证和快速处理方式。 在游戏中,日志分为:

1、系统日志,主要记录游戏服务器的系统情况。比如:数据库能否正常连接,服务器是否正常启动,数据是否正常加载;

2、玩家行为日志,比如玩家发送了什么请求,得到了什么物品,消费了多少货币等等;

3、统计日志,这种日志是对游戏中所有玩家某种行为的一种统计,根据这个统计来分析大部分玩家的行为,得出一些共性或不同之处,以方法运营做不同的活动吸引用户消费。

在构架设计中,日志记录一定要做为一种强制行为,因为不强制的话,可能由于某种原因某个功能忘记加日志了,那么当这个功能出问题了,或者运营跟我们要这个功能的一些数据库,就傻眼了。又得加需求,改代码了。日志一定要设计一种良好的格式,日志记录的数据要容易读取,分解。日志行为可以用枚举描述,在功能最后的处理方法里面加上这个枚举做为参数,这样不管谁在调用这个方法时,都要去加参数描述。 俗话说,工欲善其事,必先利其器。

游戏管理工具

游戏管理工具是对游戏运行中的一系列问题处理的一种工具。它不仅是给开发人员用,大多数是给运营使用。游戏上线后,我们需要针对线上的问题进行不同的处理。不可能把所有问题都让程序员去处理吧,于是程序员们想到了一个办法,给你们做一个工具,你们爱谁处理谁处理去吧。

游戏管理工具是一个不断增涨的系统,因为它很多时候是伴随着游戏中遇到的问题而实现的。相当于 web 的后台管理系统了。

一些功能是必须要有的,如:

1、服务器管理,主要负责服务器的开启、关闭,服务器配置信息,玩家信息查询
2、玩家管理,比如踢人、封号
3、统计查询,玩家行为日志查询,统计查询,次留率查询,邮件服务,修改玩家数据等

根据游戏的不同要求,凡是可以能过工具实现的,都做到游戏管理工具里面。它是针对所有服务器的管理。

一个好的,全的游戏管理工具,可以提高游戏运营中遇到问题处理的效率,为玩家提供更好的服务。

公共组件

公共组件是为游戏运行中提供公共的服务。例如:

这些都是针对所有区服提供的服务,所以要单独做,与游戏逻辑分开,这样方便管理,部署和负载均衡。

还有 SDK 的登陆验证,现在手游比较多,与渠道对接里要进行验证,这往往是很多 http 请求,速度慢,所以这个也要拿出来单独做,不要在游戏逻辑中去验证,因为网络 IO 的访问时间是不可控制的,http 是阻塞的请求。

一个游戏服务器起码有几个大的功能模块组成:

1、游戏逻辑工程
2、日志处理工程
3、充值工程
4、游戏管理工具工程
5、用户登录工程
6、公共活动工程

根据游戏的不同需要,可能还有其它的。所在构架的设计中,一定要考虑到系统的分布式部署,尽量把公共的功能拆出来做,这样可以增强系统的可扩展性。

技术基础

1、网络

理解 TCP/IP 协议,网络传输模型,滑动窗口技术,建立连接的三次握手与断开连接的四次挥手,连接建立与断开过程中的各种状态,TCP/IP 协议的传输效率

2、掌握常用的网络通信模型

select、epoll、poll 相关知识,学一下 libevent、libuv

3、存储

计算机系统存储体系,程序运行时的内存结构,计算机文件系统,页表结构,内存池与对象池的实现原理,应用场景与区别,关系数据库 MYSQL,共享内存

4、程序

对 C/C++有较深的理解不断学习,理解接口、封装与多态,理解常见数据结构数组、链表、二叉树、哈希表,以及排序算法等

防御式编程

不要相信客户端数据,一定要检验。作为服务器端你无法确定你的客户端是谁,你也不能假定它是善意的,请做好自我保护。(这是判断一个服务器端程序员是否入门的基本标准) 务必对于函数的传人参数和返回值进行合法性判断,内部子系统,功能模块之间不要太过信任,要求低耦合,高内聚。 插件式的模块设计,模块功能的健壮性应该是内建的,尽量减少模块间耦合。

设计模式

道法自然。不要迷信,迷恋设计模式,更不要生搬硬套 简化,简化,再简化,用最简单的办法解决问题,设计本天成,妙手偶得之。

网络模型

数据持久化

内存管理

日志系统

通信协议

全局唯一 key(GUID)

多线程与同步

状态机

数据包操作

状态监控

包频率控制

基于每个玩家每条协议的包频率控制,瘫痪变速齿轮。

在网络通信中,包频率控制是一种用于管理数据包发送速率的机制。它的目的是确保网络资源的合理利用,避免网络拥塞和性能问题。然而,如果包频率控制不合理或受到恶意攻击,可能导致网络瘫痪或变速齿轮效应。

瘫痪指的是网络过载或拥塞,导致网络无法正常工作或性能严重下降。如果每个玩家发送过多的数据包,超过网络的承载能力,那么网络可能无法处理这些包,导致延迟增加、丢包增多或连接断开等问题。

变速齿轮效应是指网络中某个节点的传输速度变化对整个网络的影响。如果某个玩家发送的数据包频率过快或过慢,可能会导致其他玩家的数据传输受到影响,引起网络不稳定或性能下降。

为了避免这些问题,网络协议和应用程序通常采取一些策略来进行包频率控制和流量管理,例如拥塞控制算法、流量限制和优先级设置等。此外,网络管理员也需要监控和管理网络资源,确保网络的稳定性和性能。

开关控制

每个模块都有开关,可以紧急关闭任何出问题的功能模块

反外挂反作弊

热更新

防刷

防崩溃

性能优化

运营支持

服务器划分

服务器划分基本原则:

1、分离游戏中占用系统资源(cpu,内存,IO 等)较多的功能,独立成服务器。
2、在同一服务器架构下的不同游戏,应尽可能的复用某些服务器(进程级别的复用)。
3、以多线程并发的编程方式适应多核处理器。
4、宁可在服务器之间多复制数据,也要保持清晰的数据流向。
5、主要按照场景划分进程,若需按功能划分,必须保持整个逻辑足够的简单,并满足以上 1,2 点。

                           AI Server
                            / \
                             |
ItemMgr Server<--------  Scene Server----->DB Server
                             / \
                              |
                         World Server ----> Phys Server
                          / \
                           |
                           |
                         Gateway

Gateway 是应用网关,用于保持合 client 的连接,该服务器需要两种 IO: 1、对 client 采用高并发连接,低吞吐量的网络模型,如 IOCP(windows)等
2、对服务器采用高吞吐量连接,如阻塞或异步 IO。

网关主要作用:
1、分担了网络 IO 资源,同时,也分担了网络消息包的加解密,压缩解压等 cpu 密集的操作。
2、隔离了 client 和内部服务器组,对 client 来说,它只需要知道网关的相关信息即可(ip 和 port)。client 由于一直和网关保持常连接,所以切换场景服务器等操作对 client 来说是透明的。
3、维护玩家登录状态。

World Server 是一个控制中心,它负责把各种计算资源分布到各个服务器,它具有以下职责:

1、管理合维护多个 scene server 2、管理合维护多个功能服务器,主要是同步数据到功能服务器 3、复杂转发其他服务器合 gateway 之间的数据 4、实现其他需要跨场景的功能,如组队、聊天、帮派等

Phys Server 主要用于玩家移动,碰撞等检测。 所有玩家的移动类操作都在该服务器上做检查,所以该服务器本身具备所有地图的地形等相关信息。具体检查过程是这样的:首先,Worldserver 收到一个移动信息,WorldServer 收到后向 Phys Server 请求检查,Phys Server 检查成功后再返回给 world Server,然后 world server 传递给相应的 Scene Server。

Scene Server 场景服务器,按场景划分,每个服务器负责的场景应该是可以配置的。理想情况下是可以动态调节的。

物品管理服务器,负责所有物品的生产过程。在该服务器上存储一个物品掉落数据库,服务器初始化的时候载入到内存。任何需要产生物品的服务器均与该服务器直接通信。

AIServer 又一个功能服务器,负责管理所有 NPC 的 AI。AI 服务器通常有 2 个输入:

一个是 Scene Server 发送过来的玩家相关操作信息
另一个时钟 Timer 驱动

在这个设计中,对其他服务器来说,AIServer 就是一个拥有很多个 NPC 的客户端。AIserver 需要同步所有与 AI 相关的数据,包括很多玩家数据。由于 AIServer 的 Timer 驱动特性,可在很大程度上使用 TBB 程序库来发挥多核的性能。

把网络游戏服务器分拆成多个进程,分开部署。

这种设计的好处是模块自然分离,可以单独设计。分担负荷,可以提高整个系统的承载能力。 缺点在于,网络环境并不那么可靠。跨进程通讯有一定的不可预知性。服务器间通讯往往难以架设调试环境,并很容易把事情搅成一团糨糊。而且正确高效的管理多连接,对程序员来说也是一项挑战

未来畅想

如果我们要做一个底层通用模块,让后续开发更为方便。到底要解决怎样的需求?这个需求应该是单一且基础的,每个应用都需要的。 正如 TCP 协议解决了互联网上稳定可靠的点对点数据流通讯一样。游戏世界实际需要的是一个稳定可靠的在游戏系统内的点对点通讯需要。 我们可以在一条 TCP 连接之上做到这一点。一旦实现,可以给游戏服务的开发带来极大的方便。 可以把游戏系统内的各项服务,包括并不限于登陆,拍卖,战斗场景,数据服务,等等独立服务看成网络上的若干终端。每个玩家也可以是一个独立终端。它们一起构成一个网络。在这个网络之上,终端之间可以进行可靠的连接和通讯。 实现可以是这样的: