游戏服务器端开发要点
本文作为游戏服务端开发的基本大纲,是游戏实践开发中的总结。第一部分专业基础,列举部分游戏开发理论知识点,第二部分游戏开发入门,讲述游戏服务器端开发的基本要点,第三部分服务端架构,介绍架构设计中的一些基本原则。
一、专业基础
1.1、网络
1.1.1、理解 TCP/IP 协议
- 网络传输模型
- 滑动窗口技术
- 建立连接的三次握手与断开连接的四次挥手
- 连接建立与断开过程中的各种状态
- TCP/IP 协议的传输效率
思考
1、TIME_WAIT 状态怎么解释?
1.1.2、掌握常用的网络通信 IO 模型
- select、poll、epoll
- epoll,边缘触发 ET 与水平触发 LT 的区别与应用
1.2、存储
- 计算机系统存储体系
- 程序运行时的内存结构
- 计算机文件系统,页表结构
- 内存池与对象池的实现原理,应用场景与区别
- 关系数据库 MySQL 的使用
- 共享内存
1.3、程序
- 对 C/C++ 语言有较深的理解
- 深刻理解接口,封装与多态,并且有实践经验
- 深刻理解常用的数据结构:数组、链表、二叉树、哈希表
- 熟悉常用的算法及相关复杂度:冒泡排序、快速排序、堆排序
二、游戏开发入门
2.1、防御式编程
不要相信客户端数据,一定要检验。作为服务器端你无法确定你的客户端是谁,你也不能假定它是善意的,请做好自我保护。(这是判断一个服务器端程序员是否入门的基本标准)
务必对于函数的传人参数和返回值进行合法性判断,内部子系统,功能模块之间不要太过信任,要求低耦合,高内聚
插件式的模块设计,模块功能的健壮性应该是内建的,尽量减少模块间耦合
2.2、设计模式
道法自然。不要迷信,迷恋设计模式,更不要生搬硬套
简化,简化,再简化,用最简单的办法解决问题
2.3、网络模型
- 自造轮子: select, epoll, epoll 一定比 select 高效吗?
- 开源框架: libevent、libev、ACE、muduo、skynet
2.4、数据持久化
- 自定义文件存储,如《梦幻西游》
- 关系数据库: MySQL
- NO-SQL 数据库: MongoDB、Redis
选择存储系统要考虑的因素:稳定性,性能,可扩展性
2.5、内存管理
- 使用内存池和对象池,禁止运行期间动态分配内存
- 对于输入输出的指针参数,严格检查,宁滥勿缺
- 写内存保护。使用带内存保护的函数 (strncpy、memcpy、snprintf、vsnprintf 等),严防数组下标越界
- 防止读内存溢出,确保字符串以“\0”结束
2.6、日志系统
- 简单高效,大量日志操作不应该影响程序性能
- 稳定,做到服务器崩溃时日志不丢失
- 完备,玩家关键操作一定要记日志,理想的情况是通过日志能重建任何时刻的玩家数据
- 开关,开发日志要加级别开关控制
2.7、通信协议
- 采用 PDL(Protocol Design Language), 如 Protobuf,可以同时生成前后端代码,减少前后端协议联调成本,扩展性好
- JSON,文本协议简单、自解释、无联调成本、扩展性好,也很方便进行包过滤以及写日志
- 自定义二进制协议,精简,有高效的传输性能,完全可控,几乎无扩展性
2.8、全局唯一 Key(GUID)
- 为合服做准备
- 方便追踪道具,装备流向
- 每个角色,装备,道具都应对应有全局唯一 Key
2.9、多线程与同步
- 消息队列进行同步化处理
2.10、状态机
- 强化角色的状态
- 前置状态的检查校验
2.11、数据包操作
- 合并,同一帧内的数据包进行合并,减少 IO 操作次数
- 单副本,用一个包尽量只保存一份,减少内存复制次数
- AOI 同步中减少中间过程无用数据包
2.12、状态监控
- 随时监控服务器内部状态
- 内存池,对象池使用情况
- 帧处理时间
- 网络 IO
- 包处理性能
- 各种业务逻辑的处理次数
2.13、包频率控制
- 基于每个玩家每条协议的包频率控制,瘫痪变速齿轮
2.14、开关控制
- 每个模块都有开关,可以紧急关闭任何出问题的功能模块
- 包频率控制可以消灭变速齿轮
- 包 id 自增校验,可以消灭 WPE
- 包校验码可以消灭包拦截篡改
- 图形识别码,可以踢掉99%非人的操作
2.15、热更新
- 核心配置逻辑的热更新,如防沉迷系统,包频率控制,开关控制等
- 代码脚本热更新,如 Erlang,Lua 等
2.16、防刷
- 关键系统资源(如元宝,精力值,道具,装备等)的产出记日志
- 资源的产出和消耗尽量依赖两个或以上的独立条件的检测
- 严格检查各项操作的前置条件
- 校验参数合法性
2.17、防崩溃
- 系统底层与具体业务逻辑无关,可以用大量的机器人压力测试暴露各种 bug,确保稳定
- 业务逻辑建议使用脚本
- 系统性的保证游戏不会崩溃
2.18、性能优化
- IO 操作异步化
- IO 操作合并缓写 (事务性的提交 db 操作,包合并,文件日志缓写)
- Cache 机制
- 减少竞态条件 (避免频繁进出切换,尽量减少锁定使用),多线程不一定优于单线程,多线程不一定比单线程快
- 减少内存复制
- 测试,用数据说话
2.19、运营支持
- 接口支持:实时查询,控制指令,数据监控,客服处理等
- 实现考虑提供 Http 接口
2.20、容灾与故障预案
三、服务器端架构
3.1、什么是好的架构
- 满足业务要求
- 能迅速的实现策划需求,响应需求变更
- 系统级的稳定性保障
- 简化开发。将复杂性控制在架构底层,降低对开发人员的技术要求,逻辑开发不依赖于开发人员本身强大的技术实力,提高开发效率
- 完善的运营支撑体系
3.2、架构实践的思考
- 简单,满足需求的架构就是好架构
- 设计性能,抓住重要的20%, 没必要从程序代码里面去抠性能
- 热更新是必须的
- 人难免会犯错,尽可能的用一套机制去保障逻辑的健壮性
3.3、架构分层
一般地,会把游戏服务器的架构划分如下三层:网络接入层、游戏逻辑层、数据存储层,这样划分的主要目的是:
1、将底层通信与业务逻辑处理解耦合;
2、将业务逻辑处理与数据存储解耦合;
3、有利于运营部署与扩展;
3.3.1、网络接入层
网络接入层主要任务是解决来自客户端大量并发请求和负载均衡的处理,考量该层的主要性能指标是:高吞吐、低延迟、均负载,即既能同一时刻处理大批量的客户端请求(每秒至少1万个请求以上),又能快速响应,并且均负载的分布处理这些请求。
- 高并发处理
1、http 接入
基于 web 的应用,玩家无需下载客户端,也不用担心防火墙等问题
2、socket 接入
二进制协议较文本协议能大幅度节省带宽,并且通信包可做组播等处理,以有效降低流量
- IP 路由与负载均衡
这块跟服务器集群和 IDC 部署密切相关,主要是为了让整个服务器集群能最大化其处理能力,尤其是在业务高峰时期,整个服务器集群能均衡平滑的应对客户端请求,不至于出现某个单点服务器出现很高负载时,其它同层服务器较空闲的情况。
3.3.2、游戏逻辑层
游戏逻辑层主要是处理游戏的具体业务逻辑,根据游戏类型和部署的不同,它会由一个或多个进程组成。
基础系统
1、游戏对象内存管理:这是业务系统中最基本也是最重要的系统之一,目前,我们采用基于共享内存的预分配机制,来管理游戏中各个对象所需内存的分配与回收。这样的好处是,当游戏服务器进程 Crash 时,配合运营的实时监测机制,系统自动拉取进程,在线玩家的状态数据可以无损恢复,并且在线玩家不会感觉到服务器宕机;
2、消息分发管理:集中处理 C->S 消息和 S->S 消息,设计时重点考虑程序的可扩展性;
3、系统与运营日志管理:分别用来监控服务器状态和玩家的各种行为;
4、游戏商城管理:对付费物品的上架、扣除、计费等处理;
5、玩家登录管理:玩家登录游戏时的流程统一处理;
业务系统
业务系统主要是说明游戏的主体内容是由哪些子模块组成,这跟具体的游戏类型关系较大。
1、地图与副本管理:游戏各公共场景和玩家独自的副本地图的处理,包括 Npc 与怪物分布、传送点分布、地图阻挡数据等的解析,以及地图实例和副本实例的抽象等;
2、移动管理:主要是实现游戏对象(玩家角色、怪物等)的地图寻路、障碍物检测,以及动态碰撞处理等功能。
3、装备与道具管理:主要包括装备的合成、拆分、打造、镶嵌、升星等,以及道具的获取、交易、使用等功能;
4、任务与事件管理:主要包括任务的领取、任务节点的更新、任务的完成和失败处理等,以及系统随机事件的产生等内容;
5、游戏世界状态管理:可将整个游戏世界各游戏对象的状态分成几大类与几小类,如:玩家角色的状态、技能 Buff 的状态等,然后对各状态之间关系进行统一管理;
6、战斗与技能管理:处理 PVE、PVP 战斗流程、伤害计算,以及各个技能、Buff、Debuff 的业务规则处理;
7、Npc 与怪物 AI 管理:包括 Npc 在场景中的分布规则和本身的功能处理,以及怪物的分布、刷新、各类 AI 行为的处理;
8、视野管理:包括玩家的视野、Npc 和怪物的视野等,设计时需要特别注意考虑各个不同场景中玩家的视野大小和视野搜索网格大小这两个重要参数,因为,它们对服务器的性能(CPU 和流量)影响很大;
9、宠物与坐骑管理:包括宠物和坐骑的养成、交易、附带技能和装备等功能;
10、社会关系管理:包括玩家组队、玩家好友、玩家交易、家族、公会、阵营、国家等社会关系功能的处理;
11、邮件管理:通过邮件可实现发送系统消息、发放系统道具,玩家道具交易等功能;
12、聊天管理:包括玩家点对点聊天、群聊等功能;
3.3.3、数据存储层
数据存储层是整个服务器的关键基础系统之一,因为游戏服务器的核心功能之一就是存取玩家数据。游戏类型不同,对数据的存取需求也不一样,对于传统客户端 MMORPG 而言,一般采用 Mysql 作数据持久化,然后在 Mysql 与 GameSvr 中间实现一个 ORM 的服务提供数据的代理或缓存即可。而新兴的 Social Game 和强交互的 WebGame,则选择用 NoSql 来替代 Mysql,以满足其超大规模 IO 并发的需求。
网络游戏服务器与数据库的关系
一般就是每个区服对应一个数据库,比如合服就是在合并数据库,有时候两个区因为版本问题、数据库架构不同,也可能会有细微不同。访问方面一般实时数据都在内存里,通过缓存和日志的方式每隔一段时间持久化一次,同时保证数据完整性。
而所谓服务器就是一组程序,用来响应来自客户端的消息。平衡负载可以简单让不同的程序负责处理不同的消息,比如一个负责战斗、一个负责聊天等,或者每个程序负责不同的地图区域之类的,这个取决于架构师的设计。不过大部分的运算都只访问内存中的数据,数据库只负责保证数据持久和完整。
一般有两种库保存玩家的不同信息
- 玩家登录账号的资料信息、账号内的余额,一般保存在一个库中。这个库在该公司内是通用的,登录官网可以登录查询充值,在每个游戏登录的时候都会访问,在游戏过程中的消费可以回馈到这个库里。
- 每个游戏的每个大区的每个服还会对应一个数据库。在这个库里,会保存玩家的角色信息(等级,装备,金钱……),玩家的社交关系,拍卖行,任务等一切在游戏里的数据。
ORM
ORM 即为对象关系映射,可以这样来简单的理解:我们平时操作关系型数据库,需要业务自己编写许多数据访问的代码,这样会把许多 SQL 语句直接暴露在业务代码里,十分不利于维护。利用 ORM 技术,可以避免这个问题,即业务逻辑不会直接对 DB 进行操作,而改由 ORM 来完成,业务逻辑与 ORM 服务之间约定相关协议和 API 接口,来完成对数据的增、删、改、查等功能。
NoSql
NoSql,指的是非关系型的数据库,它可以满足对数据库的高并发读写、对海量数据的高效率存储和访问,以及对数据库的高可扩展性和高可用性等需求,它是随着 SNS 和 Web2.0 的兴起而产生的新兴存储技术。对于游戏而言,目前它主要应用于 Social Game 和强交互的 WebGame 中。
3.3.4、通用组件层
通用组件库是指那些与具体业务无关的,能应用于所有服务器开发的组件库。
后台服务应用框架
后台服务应用框架规范了进程的起停方式、信号处理、命令行参数等操作,框架本身实现了一个 daemon 服务所必备的消息主循环、reload 机制,以及定时器处理等功能。
日志组件
日志是服务器调试和定位 Bug 的主要手段之一,日志组件一般需要实现日志分级、异步写磁盘、以及按日期时间分类等功能。
进程间通讯组件
进程间的通讯也是服务器的核心基础功能之一,高效的进程间通讯是服务器性能的基本保障,进程间通讯组件需要实现同机器与跨机器的进程高效通讯。
进程内线程之间的通信
- 指令消息
消息解包后,可以采取无锁的消息队列,然后工作线程进行业务处理。 - 连接转移
连接验证、同步、加入 IO 主线程,这些需要互斥加锁处理。
进程之间的通信
- 传统的 TCP 连接
- 绑定在 TCP 上的 RPC 连接
消息打包组件
在许多通信程序中,需要定义一套网络协议,并需要根据网络协议对协议数据单元(PDU)进行 Pack/Unpack,以实现可移植性和网络传输的可靠性及效率。但是对协议数据单元进行 Pack/Unpack 是一个重复性很强的操作,繁琐而且容易出错(Pack 和 Unpack 容易不匹配)。而消息打包组件则简化了网络协议的制定,使得业务应用不用关心协议的 Pack/Unpack 操作,并且支持良好的数据扩展和协议版本兼容。
四、服务器系统设计
服务器主要关注的点有:安全、性能、容灾、扩展。
1、安全:就是玩家用外挂伪造客户端发包怎么办?答案就是加上一堆检验。但是怎么检验才合理高效,这个就需要好好考虑了。
2、性能:就是你这个服务器撑不了几个人,很卡怎么办?答案就是各种优化,多线程、缓存、负载均衡、排队、异步,能用上的都用上。
3、容灾:就是万一你的服务器挂了怎么办?答案就是需要支持快速切换、拉起,隔离灾难,非核心进程挂掉不影响核心进程。另外怎么监控服务器,自动化处理也有需要考虑的。
4、扩展:就是如果玩家突然多了,服务器可以动态扩展吗?如果服务器需要修复 Bug,可以动态更新吗(热更新)?这就需要有对应的状态管理和更新机制。
4.1、3DMMORPG 技能战斗系统
1、技能施法时,client 只有一个上行的请求施法包,后续施法的过程全由 server 来驱动下发施法各阶段的结果信息,如吟唱、效果伤害、命中信息等等;
2、弹道类技能是由 server 计算飞行时间,并不考虑飞行后的轨迹,若此时目标有移动,则 client 会做目标跟随的处理;
3、技能学习:由秘籍来获得技能,一本秘籍包含多个技能,被动技能不影响主动技能的属性;
4、Buff 系统与技能系统是相互独立的,相互之间有各自的接口进行访问;
5、玩家施法作群攻目标选定时,也是由 server 来完成,这时是从玩家自己的视野里搜索,而无需再搜一次动态区域(格子32*32);
6、技能效果由一个主效果 +N 个 Buff 效果组成;
7、Buff 对象区分玩家和怪物,其存储结构独立出来放在内存池中,在施法者需要时再根据目标类型来添加;
8、在作技能效果计算时,尽量避免有轮循的处理,因为这样很耗 CPU。
关于技能引发的位置同步问题
1、对于改变目标(玩家)移动速度的技能,可能会带来位置不同步的问题,即 server 下发速度改变的包时,目标玩家可能正在移动,从而导致 server 和 client 的位置不一致?
答:对于这个问题,一般是通过移动系统自身的位置同步策略来解决的,即移动系统发现 client 和 server 位置不一致时,通过一定的策略来补偿速度慢的一方,从而使目标玩家在接下来的一定时间内达到位置平衡。
2、类似性质的问题还有给目标加冰冻、定身等 Buff,也可能带来位置不同步的问题?
答:这个问题暂也还没有好的解决方案,目前的做法是当目标中定身 Buff 时,client 立即表现定身,即定在原地,并将此时的位置信息带给 server,server 检验合法后设置这个位置,解冻后 client 继续让玩家移动(若玩家是移动中被定身住的话)。
冲锋技能
1、在 MMORPG 游戏中,对于那些同时拥有近攻和远程系等多种职业的游戏,策划一般都会对近攻类的职业,加上冲锋类的技能,以便平衡远程类职业在攻击距离上的优势。在 client 看到的效果,是玩家边播放冲锋动作,然后快速接近目标,并对目标一个伤害,而对 server 而言,该技能与其它技能在处理的不同之处在于,施法玩家在施法的同时,也作一个位置的变更。一般 server 的处理流程是,先检查冲锋直线路径是否合法(有无阻挡)和其它施法条件,若通过,则告诉 client 可以施法,并同时设置当前玩家坐标为冲锋后的坐标(该坐标由 client 带上来)。
2、由于 server 移动系统在对 client 发过来的移动数据作检验时,需要检查本次移动的起步点,是否为上次移动的结束点,即作线段端点合法检查,而冲锋到达的目标点并未在上次移动的路径栈信息中,所以,server 技能系统在设置冲锋位置时,需要先清除原路径栈信息(如调用 MoveStop 接口),以便确认本次冲锋为一次全新的移动。
3、两个玩家同时使用冲锋技能时(目前 client 在做技能时,采用先表现的方式),出现一个玩家(冲锋目标 client)停在冲锋途中的现象,原因为 server 广播技能施法时,没有带目标主动位移的信息(client 在处理冲锋位置时,对于使用冲锋技能的 client,因为它知道冲锋到达的位置,所以它的处理没有问题,而另一个冲锋目标 client,由于它不知道对方要冲到哪里,则 client 处理时就是选择冲锋路径上的一点,这样就会看到停在冲锋途中了)所致。解决办法是,要么在协议中下发目标位移信息,要么在策划规则上,只允许同一时刻一个玩家冲锋,如冲锋加晕眩 debuff 等。
4.2、Social Game 与 MMORPG 在服务器架构上的差异
协议通信
Socail Game 为了快速开发,在通信协议的选择上均会选择 http 作为底层通信协议,这样选择的好处大致有:
1、方便客户端编程:http 为一应一答的同步模型;
2、可以选择成熟的开源 http 服务器,如:apache、nginx;RPG选择的是基于 socket 的 TCP 通信,这是由游戏本身的特点所决定的,如:聊天、多人视野、服务器主动通知事件等需求
游戏逻辑服务器的承受能力
1、RPG 的游戏逻辑服务器(地图服务器)所能承载的最高在线(PCU)是在3000-5000不等;
2、Social Game 由于没有 RPG 复杂的移动、战斗、视野管理等需求,逻辑服务器承受的在线一般都是比较高的,如10000-30000不等
游戏逻辑服务器的 Cache 和回写机制
1、RPG 的游戏逻辑服务器一般都需要 Cache 玩家数据,并且采取定时回写的策略,如根据数据重要程度分别作 5min-15min 不等的定时回写;
2、Social Game 的游戏逻辑服务器一般都无需 Cache 玩家数据,玩家的每次请求都是即时读即时写(这样会涉及到另外一个问题,即 DB 读写的性能问题,见下一条);
DB 存储模型的选择
1、RPG 存储服务器常用的还是 MySQL(Innodb 存储引擎),中间还由业务自己写一个 Cache 代理服务器,以 Cache 热点数据;
2、Social Game 则会选用 TC、MemCached 等所谓 Key-Value 全内存数据存储,有实力的公司会自己实现一个类似这种机制的存储系统,它可以无源,也可以是有源,并且还可以选择用 MySQL 作数据落地,毕竟 MySQL 作为互联网业务 DB 的成熟解决方案已毋庸置疑;
交互数据的一致性
1、在 SNS Game 中,会经常出现同一个玩家在某一个时刻同时被多个好友访问和修改数据的情况,这时就需要保证,每次数据的更新都是正常有序进行的,而不能被写脏数据。一般地,都会使用一个类似全局锁服务的设计来解决这个问题;
2、RPG 不会存在这样的问题,类似的需求是:玩家可能会跨地图服务器,即所谓的跳线或跨服,这个问题的通常解决方案是服务器重新作一次下线、重登录的处理,当然,玩家是感觉不到的;
IDC 部署
1、RPG 一般是分区分服部署;
2、Social Game 则是全区全服部署;
4.3、游戏排行榜的实现
游戏开发中排行榜经常出现,实际开发端游项目遇到的排行榜是根据玩家的某种特征对所有玩家进行排序(装备评分、人物等级…等)。排行榜需求不是实时显示,而且客户端也只显示 TopK(K=10、50、100) 的排名玩家。
排行榜的主要作用:
1、排行榜内容是部分有序的玩家信息(以等级为例)
2、玩家等级变化后,需更新排行榜
3、根据玩家 ID 获取其在排行榜中的排名
4、获取排行榜中特定排名的玩家
4.4、卡牌游戏和弱连接
随着游戏类型复杂度提升,在一种游戏里,以某一种核心内容为主(卡牌),其他内容为辅(横版、PVP 等)的做法比较常见。但并非百分百的卡牌游戏都固定地使用弱连接模式,可以通过游戏的表现来判断是否使用弱连接。基于 PVP 的判断方式有如下两种:
第一种
1、战斗过程中,选择的内容偏多,且时间偏长(如放牌)
2、战斗允许预先放置卡牌和预先连锁判断
3、客户端使用网页形式来制作
第二种
1、没有即时交互的过程,玩家和玩家之间的对战摆完道具就自动进行计算和战斗
2、刷新排行榜
3、买卖道具
安全性
在服务器登录的时候,进行一下简单的校验,比如使用时间戳或者预定随机值或者预定好的 counting 值来进行哈希,哈希后的值用作双方通信的 key 来进行加密通信,这样以后每次通信都不需要再次加密,时间戳或者随机值加上哈希可以保证每次登录的 key 不同。
排行榜的推送和积分,购买道具的选择和下单购买,这些都基本使用弱连接。
4.5、地图的无缝连接
所谓的无缝,最大的难点就是连接点,只要解决了连接点之间的通信和客户端之间的交互,就解决了无缝问题。
4.6、帧同步中如何做移动预测
王者荣耀,采用的是帧同步方案。不存在预测的情况,都是得在相应帧做相应的输入操作。如果到了相应帧,因为网络过卡,没有及时收到输入数据,则会锁帧等待,表现出来就是网络差时,会卡住,后来收到包时,再追帧。但是不影响其它玩家。
一般做预测都是用在状态同步里面,帧同步讲究的是根据操作得出确定的结果,如果网络出现问题有可能会进行等待的情况。
4.7、包裹系统
包裹中增加道具
道具如果是不能叠加的
1、往包裹中添加道具,主要是往空位置上插入
2、有空位置,则加入到道具管理器中
3、道具有耐久度,且有扣耐久类型,比如是根据游戏时间或者物理时间,设置是时限道具
- 本文标题:游戏服务器端开发要点
- 本文作者:beyondhxl
- 本文链接:https://www.beyondhxl.com/post/a9472e98.html
- 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!