写点什么

深入浅出 node.js 游戏服务器开发——基于 Pomelo 的 MMO RPG 开发

  • 2013-03-26
  • 本文字数:4942 字

    阅读完需:约 16 分钟

在上一篇文章中,我们介绍了如何使用 Pomelo 来搭建聊天服务器。在这篇文章中,我们为大家介绍如何使用 Pomelo 框架来搭建 MMO RPG 服务器,并分析其设计思路和实现方法。以此来帮助大家更好的理解和使用 Pomelo 框架,理解 Pomelo 框架游戏开发的基础流程,使用方法和设计理念。

本文中的游戏服务端架构,只是为了说明 Pomelo 的开发理念和设计思路,并不是基于 Pomelo 开发的唯一方案,开发者完全可以根据自己的实际应用环境设计不同的服务端架构。

开始之前

Pomelo 框架与 MMO RPG

我们曾在本系列第一篇文章曾介绍过 pomelo 的架构。我们先简单回顾一下 Pomelo 为我们的游戏开发提供了什么:

  • 可扩展的服务器架构。Pomelo 中对服务器端进行了抽象,将服务器分为承载链连接的前端服务器和负责业务逻辑的后端服务器,并提供了便利,高效的分布式扩展支持,让使用者可以在少改甚至不改的前提下实现对服务端的扩展。
  • 完整的通信框架:Pomelo 对客户端与服务器之间,服务器与服务器之间的通信进行了完整的封装。开发者只需要按照默认规则来填写服务代码就可以完成接口的发布和调用,而不用考虑内部实现。
  • 大量游戏开发基础库:除了基本的服务器框架之外,Pomelo 还提供了很多游戏开发需要用到的基础库,如任务调度,AI 控制,寻路等,而这些库还会随着 Pomelo 的发展进一步完善。
  • 基于 Node.JS 轻量级的开发环境,以及大量的模块。相对于传统的开发语言,Node.JS 有着轻量,快捷的特性(0.3 版中启动一个包括 10 几个服务器集成的服务端只需要不到 4S)。而活跃的开源社区也提供了大量的第三方模块。

作为 Pomelo 游戏开发的入门导引,本文的重点将放在游戏基础架构的搭建上,因此本文将主要介绍下面三个方面的内容:游戏服务端的构建,与客户端的通讯,服务器的扩展。

本文的参考示例

我们使用 demo Treasures 作为本文的参考示例,游戏的截图如下:

从上图可以看出,在 treasures 中,玩家会进入一张遍布宝物的地图中,通过拾取宝物来获得积分。所有玩家的积分在右上角会有一个排名。下面是这个 demo 的关键点:

  • 每个玩家的行动对其他玩家来看都是实时的。
  • 在获取宝物时积分会更新积分榜,这个更新对所有玩家实时可见。
  • 宝物会定时刷新。

相对于一般的的 MMO RPG,这个 demo 显得十分简陋:没有持久化,没有战斗,没有 AI。。。但是,其中实现了 MMO RPG 中最核心的亮点功能:一个可以容纳多个在线玩家的游戏场景,以及玩家之间的实时互动。那些功能的确实可以让系统的结构更加清晰明确,成为一个非常好的项目导引。

搭建游戏服务端

由于游戏逻辑十分简单,我们后端采用一台单独的场景服务器来运行整个游戏逻辑,同时加入一台连接服务器来承载用户连接,系统的设计如下:

下面,我们就按照这个设计来搭建游戏服务端。

编写场景服务

游戏场景是玩家所处的虚拟环境,而场景服务器就是游戏场景在服务端的抽象,根据游戏类型和内容的不同,场景服务器的复杂程度也会千差万别。在我们的例子中,场景的构成十分简单:一张开放的游戏地图,地图中的玩家,以及定时刷新的宝物。

首先,作为一个场景服务器,需要能够储存用户和宝物的信息,这里我们直接使用一个放在内存中的 map 来储存场景中的所有实体,同时,我们将所有场景中的实体都抽象为一个 Entity 对象,放在这个 map 中。

为了能够操纵这些数据,还需要暴露出对外接口,我们使用的接口有下面三种:

  • 初始化的接口: 我们在 init 方法中会设置场景信息,配置参数等, 并启动场景中的时钟循环。
  • 实体访问接口:如 AddEntity 和 RemoveEntity 接口等,我们使用这些接口来访问和修改场景中的实体。
  • 刷新场景中的宝物:当满足条件时,外部事件会调用该接口来刷新地图中的宝物。

我们通过一个无限循环的 tick 来驱动场景服务,在每一个 tick 中会更新场景中所有实体的状态信息,我们最终设计如下:

搭建场景服务器

在完成场景服务的代码之后,我们还需要提供一个场景服务运行的平台,在 Pomelo 中,我们通过搭建一个场景服务器来实现。

Pomelo 中的服务器分为两类,负责承载用户连接的前端服务器和运行逻辑的后端服务器。作为负责核心逻辑的场景服务器,自然是属于后端服务器,因此,我们在 /game-sever/config/server.json 中加入以下配置:

"area": [ {"id": "area-server-1", "host": "127.0.0.1", "port": 3250, "areaId": 1} ]

其中的“area”是我们为场景服务器类型所起的名称,其对应的内容就是场景服务器的列表,可以看出,现在我们只加入了单台场景服务器。与聊天服务器相比,场景服务器的配置并没有明显区别,只是多了一个 areaId 的属性。我们使用这一属性来标明这个场景服务器对应的场景 id,我们的建议设计是一个游戏服务器对应一个独立的游戏场景,这样可以大大减少场景管理的开销,并提高单场景的负载能力。Pomelo 中每一个服务器就是一个独立的进程,相对于一个场景服务的开销,单独的服务器造成的开销是可以接受的。当然,这只是建议设计,框架本身完全支持一个游戏服务器对应多个游戏场景的设计。开发者可以根据具体的应用情况调整场景服务器的配置,通过加入场景管理逻辑,实现一个场景服务器和场景之间的自由配置。

启动场景服务

在加入场景服务器之后,我们还需要对服务端的运行环境进行配置,在场景服务器启动时运行对应的场景服务。为了实现这一目标,我们需要在 app.js 中加入如下配置:

app.configure('production|development', 'area', function(){ var areaId = app.get('curServer').areaId; area.init(dataApi.area.findById(areaId)); });

app.configure 方法用来对指定的服务器进行配置,包括三个参数,前两个参数分别是运行环境和服务器类型的 filter,第三个参数是在满足前面两个 filter 的情况下需要运行的代码。在上面的配置中,第一个参数"production|development"表示是针对线上和开发两种环境,之后的‘area’参数表示的是针对 area 服务器类型。 关于 app.js 初始化的更多内容,见 Pomelo 启动流程

与客户端通讯

建立连接服务器

要与客户端通信,我们需要建立一个前端服务器,用来维护与客户端之间的连接。server.json 中的配置如下:

"connector": [ {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientPort": 3010, "frontend": true} ] 其中的标志位“frontend:true”表示这是一个前端服务器,“clientPort:3010”则表示该服务器对外暴露前端。对于前端服务器,在启动时就会默认加载连接组件,因此我们不需要在 app.js 中进行额外的配置。在 pomelo 0.3 中,如果需要使用原生 websocket 等非默认的连接方式,则需要在 app.js 中加入相应配置。在客户端,我们在启动时连接对应的接口,就可以建立起与服务端的连接。

处理客户端请求

Pomelo 中,我们提供了两种客户端向服务端发送请求的方法,request/response 模式和 notify 模式。

request/response 模式与 web 中的请求模式相似,是标准的请求 / 响应模式,对于客户端的一个请求,服务端会给出一个响应。以玩家的移动为例,当需要移动时,会发送一个请求给服务端,服务端会验证客户端的请求,并返回结果客户端,下图是具体请求流程:

最上面的是客户端代码,在这里,我们向服务端发送一个移动请求。 之后,服务端会根据请求的 route(area.playerHandler.move)找到对应的处理方法(/game-server/area/handler/playerHander.move),然后调用该方法来处理客户端的请求。当处理完成之后,会并在 next 方法中传入处理结果,结果会发回客户端,并作为回调函数的参数传回。

notify 模式的运行流程与 request/response 类似,只是当服务端处理请求后不会发送任何响应。客户端通过 pomelo.notify 方法来发出 notify 请求,notify 请求的参数与 pomelo.request 相似,只是不需回调函数,notify 方法的实例如下:

```` pomelo.notify(‘area.playerHandler.pick’, params); ``` `### 服务端消息推送

与 web 服务不同,game 服务端会有大量的推送消息,要实现这一功能,我们使用 pomelo 中的 push 模式来实现。下图以“move”为例,展示了服务端的推送流程:

Treasures 中的广播功能是通过一个全局的 channel 来实现的,在游戏中的所有玩家在加入游戏后都会加入一个全局的 channel 中。当需要广播消失时,服务端就会调用这个全局的 channel 来对所有用户进行消息推送。

扩展游戏服务端

在前面两节中,我们使用 pomelo 搭建了一个单节点的游戏服务器。在这一节中,将介绍如何使用 Pomelo 来对服务端进行扩展,搭建分布式的游戏服务。

由于游戏服务器的复杂性,像 web 服务器简单的水平扩展是不现实的,而根据业务逻辑的不同,不同的服务器也有着不同的扩展方案。因此我们分别以前面介绍的连接服务器和场景服务器为例,来对他们进行扩展。

连接服务器的扩展

连接服务器作用是负责维护所有客户端的连接,负责客户端消息的接受和推送,在 MMO RPG 中,连接服务器往往是性能的热点之一,因此对连接服务器的扩展对于提高系统负载有很重要的现实意义。 在例子 treasures 中,我们通过加入一个负载均衡服务器(gate 服务器),来实现连接服务器的扩展:当客户端登录时,会首先连接 gate 服务器,来分配一个连接服务器,之后,客户端会断开与 gate 服务器的连接,在与其对应的连接服务器建立连接,如下图所示:

要实现这一功能,在服务端,我们首先要在 server.config 中加入新的前端服务器,代码如下:

```` “gate”: [ {“id”: “gate-server-1”, “host”: “127.0.0.1”, “clientPort”: 3014, “frontend”: true} ] ``` `之后,在 gate 服务器上编写对应的负载均衡接口,在例子中,我们采用了使用 uid 的 crc 值对服务器数目取模的方式, 代码如下:

```` module.exports.dispatch = function(uid, connectors) { var index = Math.abs(crc.crc32(uid)) % connectors.length; return connectors[index]; }; ``` `最后,在客户端加入对应的连接代码,就完成了对连接服务器的扩展。 在我们的例子中只用到了两台连接服务器,而在实际应用中,可以根据实际环境,编写自己的负载均衡算法,加入更多的连接服务器。

场景服务器的扩展

与连接服务器不同,场景服务器中包含着大量的状态信息,如果对单一的场景进行扩展,需要复杂的同步机制和大量远程调用。因此,在 Pomelo 中,我们的场景扩展是通过加入新的场景来进行的。 在设计游戏时,整个世界就被分为多个不同的场景。而在服务器端,一个场景则与一个独立的场景服务器相对应,场景服务的扩展可以通过加入新的场景来完成。下面,就以 treasure 为例,介绍一下场景服务的的扩展方法: 因为场景扩展是通过加入新的场景来完成的,所以首先要在 server.json 中加入新的场景服务器:

在加入场景服务器之后,我们还要保证发往场景服务器的请求可以被分发到正确的场景去。而 Pomelo 中,对于同一类型的服务器,默认的分法方法是采用随机分配的方式,这是不能满足我们要求的。因此,我们需要加入自己的路由算法,流程如下:

首先,我们编写了新的路由算法:根据用户所属的场景 id 进行投递。之后,我们在 app.js 中使用 app.route 方法来将我们的路由算法配置为针对 area 服务器的默认方法。app.route 方法接受两个参数,第一个是需要自定义 route 的服务器类型,第二个参数就是具体的路由算法。我们分别将‘area’服务类型和我们编写的算法传入,就可以实现自定义的路由功能了。

跨平台客户端

除了原有的 JS 客户端之外,pomelo 还提供了多种其他的客户端,而不同的客户端可能会对应着不同的长连接协议。但是经过 pomelo 封装之后,在 Pomelo 服务端,不同客户端在连接上连接上是完全对等的。因此,你可以使用 pomelo 框架实现跨平台联机对战的(前提是你开发了针对多个平台的游戏客户端)。

最终架构

经过扩展后,我们的最终服务器架构如图所示:

总结

在本文中,我们介绍了如何使用 Pomelo 框架来构建了一个包括连接服务器和场景服务器的 MMO RPG 服务端。并介绍了如何使用 pomelo 特性,来对游戏服务端进行扩展,并构建出了一个分布式的 MMO RPG 服务。 但是,这只是游戏服务端开发中最为基础的部分,MMO RPG 中很多基础功能在这里都没有实现,如数据持久化,AI 控制,寻路系统等。在下面的文章中,我们会进一步介绍 Pomelo 在游戏开发中的应用。

参考示例

捡宝demo

Pomelo 启动流程

Pomelo 框架

2013-03-26 03:3740198

评论

发布
暂无评论
发现更多内容

Android技术分享| ViewPager2离屏加载,实现抖音上下视频滑动

anyRTC开发者

android 音视频 移动开发 ViewPager 视频滑动

Flink CDC 系列 - 构建 MySQL 和 Postgres 上的 Streaming ETL

Apache Flink

大数据 flink 编程 后端 实时计算

「Spark从精通到重新入门(一)」Spark 中不可不知的动态优化

尔达Erda

云计算 大数据 spark 开发者 感悟

【活动报名】Apache ShardingSphere Dev Meetup 重启!

SphereEx

开源项目 开源社区 ShardingSphere Meetup SphereEx

Linux学习方法《Linux一学就会》Linux系统进程管理

侠盗安全

Linux linux运维 运维工程师 云计算架构师

语法糖甜不甜?巧用枚举实现“状态”转换限制

阿Q说代码

枚举 签约计划第二季 语法糖 订单状态转换

后端开发实战总结 | 签约计划第二季|后端

阿Q说代码

内容合集 签约计划第二季 技术专题合集

博文推荐|使用 Pulsar IO 打造流数据管道

Apache Pulsar

Java 开源 架构 云原生 Apache Pulsar

WeTest小程序质量专项方案推出,小程序异常监控内测招募中

WeTest

『上线』OpenSEC SIGs 终于成立了!

SphereEx

开源社区 ShardingSphere SphereEx 中文开源 OpenSEC

秒过!度目智慧通行让常态化防疫更高效

百度大脑

人工智能 人脸识别

如何在 ShardingSphere 中开发自己的 DistSQL

SphereEx

Java 数据库 ShardingSphere database SphereEx

实战篇:Security+JWT组合拳 | 附源码

阿Q说代码

spring security JWT 签约计划第二季 权限验证

Spark从入门到精通

冇先生

IoT Stack 2.0升级物模型及数据交互协议, 大幅提升物联网方案交付速度

百度大脑

人工智能 百度 物联网

用户登录设计之双token设计

CRMEB

看FusionInsight Spark如何支持JDBCServer的多实例特性

华为云开发者联盟

大数据 SQL语句 FusionInsight Spark JDBCServer 多实例

大数据中不同文件格式的比较

吴脑的键客

大数据 云存储

看了这么多年西游记,你可知道孙悟空是如何召唤土地公公的吗?

阿Q说代码

Java 观察者模式 签约计划第二季 事件通知机制

对象存储手把手教七 | 存储空间授权策略 Bucket Policy

QingStor分布式存储

分布式系统 对象存储 分布式存储 分布式,

实战篇:断点续传?文件秒传?手撸大文件上传

阿Q说代码

断点续传 签约计划第二季 文件秒传 文件分块 文件合并

看了同事写的代码,我竟然开始默默的模仿了。。。

阿Q说代码

策略模式 多态 签约计划第二季 自定义参数解析器 统一验签

Flink 是如何统一批流引擎的

编程江湖

大数据 flink

还在用BeanUtils拷贝对象?MapStruct才是王者!【附源码】

阿Q说代码

Java MapStruct 签约计划第二季 深拷贝与浅拷贝

【量化】股市技术分析利器之TA-Lib(一)

恒生LIGHT云社区

量化投资 量化

全程干货,用 python 下载某站全部【免抠图片】,图片背景透明,格式PNG

梦想橡皮擦

12月日更

恒源云(GPUSHARE)_GPU白嫖大法来袭!

恒源云

深度学习 gpu 算力加速

如果还不懂如何使用 Consumer 接口,来公司我当面给你讲!

阿Q说代码

函数式接口 签约计划第二季 consumer 实战讲解 supplier

PackML从会到不会——标签(3)

陈的错题集

标准化 PackML

长连接网关技术专题(六):石墨文档单机50万WebSocket长连接架构实践

JackJiang

websocket 即时通讯 IM 网关

高校企业双向赋能,首届飞桨启航菁英计划圆满结束

百度大脑

人工智能 百度 飞桨

深入浅出node.js游戏服务器开发——基于Pomelo的MMO RPG开发_架构/框架_张小刚_InfoQ精选文章