Pomelo MMORPG

环境准备

$ mkdir mmorpg && cd rpg
$ pomelo ini
$ npm-install.bat

服务器类型

游戏采用分布式设计,服务端是由一个服务器集群所组成。

序号服务器类型名称数量
1网关服务器gate1
2连接服务器connector1
3聊天服务器chat1
4认证服务器auth1
5场景服务器arean
6寻路服务器path1
7管理服务器manager1

网关服务器gate

  • 网关服务器为用户提供统一的websocket入口
  • 网关服务器会向所有客户端暴露一个固定的websocket接口
  • 网关服务器负责用户验证和连接服务器的分配
  • 网关服务器一般只有一台

当用户登录时会首先连接网关服务器完成验证并获得由网关服务器分配的对应连接服务器的地址,之后客户端会断开与网关服务器的连接,通过获取的地址连接对应的连接服务器以获取对应的服务。

连接服务器 connector

与web短连接模式不同的是,网络游戏中客户端和服务器建立的都是长连接,长连接本身需要一定的资源来维持。

本游戏中使用websocket协议在客户端和服务器之间建立连接,连接服务器是用来维护这些连接,并中转客户端和服务器之间的消息。

本游戏中客户端和服务器的连接是通过一个抽象的会话来维护的,会话是一个客户端在服务端的标识,用来维护用户的登录状态、用户的基本信息、用户的websocket连接信息等。

认证服务器auth

  • 认证服务器负责用户注册和验证,作为用户验证的统一入口。
  • 认证服务器提供远程调用接口供其它服务器调用来进行用户身份验证
  • 认证服务器的主要作用是屏蔽认证验证的细节为其它服务器提供统一的验证接口

聊天服务器 chat

  • 聊天服务器是网游的基本服务之一,通过独立的服务器实现。
  • 聊天服务器维护一份所有在线用户的数据,通过这些数据与连接服务器通讯,实现用户之间的即时通讯。

场景服务器 area

  • 网游中处于性能和负载考量,大的游戏世界会被划分为多个区域即场景。
  • 本游戏中一张地图即一个游戏场景与一台独立的场景服务器对应
  • 场景是构成游戏世界的基本单位,不能进行分隔和合并扩展。
  • 场景服务器负责维护场景中所有实体并驱动实体AI运行游戏逻辑
  • 场景服务器负责处理游戏中几乎所有的逻辑同时为其它服务器提供操纵场景数据的接口
  • 虽然场景本身不可分隔但可通过加入新的场景的方式来分散用户从而提高游戏服务器总体负载
  • 一些与场景相关的服务通过独立运行的方式进行水平扩展

寻路服务器path

  • 寻路服务器是游戏服务器基本服务之一
  • 玩家跑动、怪物移动都需要寻路服务器提供支持
  • 寻路服务器是根据地图的起点和重点得到两点之间的最优路径
  • 由于寻路是典型的无状态、计算密集型服务,因此将寻路与场景逻辑分离,放在单独的服务器中。从而减轻场景服务器的压力。
  • 寻路服务器可根据简单并行进行扩展
  • 寻路算法使用AI实现并提供通用的计算接口,并封装为一个模块。

管理服务器manager

管理服务器是后端服务器集群中负责全局管理副本全生命周期和组队相关操作的功能服务器。

组队功能模块team

组队功能是玩家之间互动的一种方式,玩家可创建队伍并邀请其它玩家加入,其它玩家也可以主动向队长踢出申请加入队伍,队伍的人数上限为3。

teamHandler.js为协议入口模块,负责负责队伍相关操作的前期判断和后期通知。teamHandler完成前期判断后通过一个rpc将操作所需的参数传递给manager服务器。队伍对象的管理工作是由manager服务器所持有一个全局的teamManager.js模块负责的,teamManager中管理所有的team对象的创建、更改、销毁等操作。

队伍id从1开始递增,所有场景中的队伍使用统一的id序列,当manager服务器重启时,队伍id也重新初始化为1.

team.js模块中维护一个队伍对象中的所有成员与成员身份,维护一个队伍频道来通知各个成员队伍相关的消息及进行队伍内的聊天。

同一个队伍中的玩家可以进入同一个组队副本。

副本功能模块instance

副本instance.js本质上是一个临时的场景,并对进入该临时场景的玩家进行限制。instance由模块instancePool.js来统一管理,这与队伍模块的结构是相同的。

目前有两种副本可供玩家进入分别是单人副本、组队副本。当队伍中有两个以上玩家时,队长点击进入组队副本时,队伍中的队员会同时拉入副本中,队伍中的队员也可以单独进入组队副本,但不会触发将队伍中的其它成员拉入副本的操作,其它成员可以分别进入该副本。

玩家进入副本也被视为场景切换,在areaService.js模块中changeArea函数中进行目标场景类型判断,如果是普通场景则正常切换。如果是副本场景则通过一个rpc来创建副本。组队副本创建时的id与队伍id相关。单人副本创建时的id与玩家id相关。组队副本创建成功后,如果是队长触发的操作则向其它队员客户都拿发送进入副本的命令,如果是队员则直接进入副本中。

服务器配置

配置游戏服务器类型

$ vim game-server/config/adminServer.json
[
    {
        "type": "gate",
        "token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
    },
    {
        "type": "connector",
        "token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
    },
    {
        "type": "chat",
        "token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
    },
    {
        "type": "auth",
        "token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
    },
    {
        "type": "area",
        "token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
    },
    {
        "type": "path",
        "token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
    },
    {
        "type": "manager",
        "token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
    }
]

配置不同类型服务器的参数

$ vim game-server/config/servers.json
{
  "development":{
    "gate": [
      {
        "id": "gate-server-1",
        "host": "127.0.0.1",
        "clientPort": 3014,
        "frontend": true
      }
    ],
    "connector": [
      {
        "id": "connector-server-1",
        "host": "127.0.0.1",
        "port": 3150,
        "clientHost": "127.0.0.1",
        "clientPort": 3010,
        "frontend": true
      },
      {
        "id": "connector-server-2",
        "host": "127.0.0.1",
        "port": 3151,
        "clientHost": "127.0.0.1",
        "clientPort": 3011,
        "frontend": true
      }
    ],
    "area": [
      {
        "id": "area-server-1",
        "host": "127.0.0.1",
        "port": 3250,
        "area": 1
      },
      {
        "id": "area-server-2",
        "host": "127.0.0.1",
        "port": 3251,
        "area": 2
      },
      {
        "id": "area-server-3",
        "host": "127.0.0.1",
        "port": 3252,
        "area": 3
      },
      {
        "id": "instance-server-1",
        "host": "127.0.0.1",
        "port": 3260,
        "instance": true
      },
      {
        "id": "instance-server-2",
        "host": "127.0.0.1",
        "port": 3261,
        "instance": true
      },
      {
        "id": "instance-server-3",
        "host": "127.0.0.1",
        "port": 3262,
        "instance": true
      }
    ],
    "chat": [
      {
        "id": "chat-server-1",
        "host": "127.0.0.1",
        "port": 3450
      }
    ],
    "path": [
      {
        "id": "path-server-1",
        "host": "127.0.0.1",
        "port": 3550
      }
    ],
    "auth": [
      {
        "id": "auth-server-1",
        "host": "127.0.0.1",
        "port": 3650
      }
    ],
    "manager": [
      {
        "id": "manager-server-1",
        "host": "127.0.0.1",
        "port": 3750
      }
    ]
  },
  "production":{}
}
序号服务器类型名称数量rpc iprpc portclient ipclient port
1网关服务器gate1--127.0.0.13014
2连接服务器connector2127.0.0.1315x127.0.0.1301x
3场景服务器area6127.0.0.1325x--
4聊天服务器chat1127.0.0.13450--
5寻路服务器path1127.0.0.13550--
6认证服务器auth1127.0.0.13650--
7管理服务器manager1127.0.0.13750--

其中场景服务器又分为两种类型分别是普通的场景服务器和副本服务器

序号服务器类型服务器名称是否副本
1场景服务器area-server-1
2场景服务器area-server-2
3场景服务器area-server-3
4副本服务器instance-server-1
5副本服务器instance-server-2
6副本服务器instance-server-3

启动流程

app.js是游戏服务器的入口,主要负责所有服务器的配置,以及组件的加载和启动。本项目的启动主要分为两步:先启动pomelo的master服务器,再由pomelo的master服务器分别启动其它服务器。

项目启动采用pomelo的启动方式,即将master作为默认组件,在app.js中调用app.start()方法后加载并启动master服务。master组件会负责启动其它服务,启动过程分为两个阶段:

  • 第一阶段
    master服务启动其它所有服务,在服务器启动完毕后,其中monitor组件会连接到master对应的监听端口上,表明该服务器启动完毕。
  • 第二阶段
    当所有服务器启动完毕后,master会调用所有服务器上的afterStart接口来执行后续处理流程。

组件的加载和配置

本项目使用多个外部组件,这些组件在服务器启动时加载以提供各种服务,诸如数据统计、路由替换、游戏场景初始化等。

自定义在线统计模块

项目中使用了基于脚本的统计,组件通过运行自定义的脚本,收集服务器运行数据并生成报告。

$ vim game-server/modules/online.js
//自定义监控模块
let Module = function(opts){
    console.log("online modules constructor");
    opts = opts||{};
    //当前监控模块所监测的服务器实例
    this.app = opts.app;
    //获取数据的方式
    // pomelo-admin提供两种方式一种是pull拉取,一种是push推送
    // pull拉取表示master服务器会主动从各个游戏服务器上拉取所需的监控数据
    // push推送表示游戏服务器向master服务器推送监控数据
    this.type = opts.type || "pull";
    //每次push或pull的时间间隔
    this.interval = opts.interval || 5;
};

// 监控模块标识
// 用于唯一标识监控模块,非常重要。
// 如果要向该模块获取和发送数据都需要依靠这个ID作为参数
// 如果需要让外部程序获取该模块的监控数据也必须依靠此参数
Module.moduleId = "online";

// 采用pull拉取 当接收到master主服务器拉通知时回调
// 采用push推送 每次到达interval间隔时间时回调
Module.prototype.monitorHandler = function(agent, msg){
    console.log("online modules handler");
    //获取连接组件服务
    const connectionService = this.app.components.__connection__;
    if(!connectionService){
        console.log("not support connection: %j", agent.id);
        return;
    }
    //代理通知连接统计信息
    const info = connectionService.getStatisticsInfo();
    //通知admin的消息给master服务器
    agent.notify(Module.moduleId, info);
};

// 采用pull拉取 每次到达pull拉取时间间隔时会被调用
// 采用push推送 当接收到游戏服务器push推送数据回调
Module.prototype.masterHandler = function(agent, msg){
    console.log("online master handler", msg, Module.moduleId);
    //若无消息则通知所有的monitor监视器去获取数据
    if(!msg){
        //通知指定类型的监听器获取获取数据
        const type = "connector";
        const list = agent.typeMap[type];
        if(!list || list.length===0){
            agent.notifyByType(type, Module.moduleId);
        }else{
            //通知所有监视器去获取数据
            agent.notifyAll(Module.moduleId);
        }
        return;
    }
    //从monitor监视器中收集数据
    let data = agent.get(Module.moduleId);
    if(!data){
        data = {};
        agent.set(Module.moduleId, data);
    }
    data[msg.serverId] = msg;
};

//当第三方程序调用时 获取监控数据接口时回调
Module.prototype.clientHandler = function(agent, msg, cb){
    console.log("online client handler");
  if(!!cb && typeof cb==="function"){
      const param = agent.get(Module.moduleId) || {};
      //处理客户端请求直接返回数据并缓存到master服务器
      cb(null, param);
  }
};

//导出模块
module.exports.moduleId = Module.moduleId;
module.exports = function(opts){
    return new Module(opts);
};

启动脚本中为所有服务器添加在线统计,也可以单独为connector连接服务器添加。

$ game-server/app.js
//应用全局配置 针对所有服务器
app.configure("production|development", function(){
    //开启系统监控 Linux环境有效
    app.enable("systemMonitor");
    //配置自定义监控:运行自定义统计脚本收集服务器运行数据,为服务器注册自定义监控模块。
    if(typeof app.registerAdmin === "function"){
        //将监控在线用户模块注册给所有服务器
        app.registerAdmin("online", require("./app/modules/online"), {app:app});
    }
});
pomelo 是由网易开发的基于node.js开发的高性能、分布式游戏服务器框架, 也可作为高实时web应用框架。 Pomelo的应用范围 pomelo最适合的应用领域是网页游戏、社交游戏、移动游戏的服务端,开发者会发现pomelo可以用如此少的代码达到强大的扩展性和伸缩性。当然还不仅仅是游戏,很多人断言未来的web时代是实时web应用的时代, 我们发现用pomelo开发高实时web应用也如此合适, 而且伸缩性比其它框架好。目前不推荐将pomelo用于大型的MMO rpg游戏开发,尤其是3d游戏, 还是需要象bigworld这样的商用引擎来支撑。 Pomelo的理念 pomelo的第一个理念是让游戏(高实时web应用)服务器的开发变得非常简单, 而不是解决某类算法或系统上的难题。这个设计理念跟rails是很类似的;第二个理念是重视性能和可伸缩性,用户用pomelo开发出来的游戏天生具有很强的伸缩性,扩展也很容易。我们在性能优化上也花了很多功夫,并且会持续进行;第三个理念是让第三方很容易扩展,框架用了很多插件式的设计, 组件component、路由规则、甚至管理控制台都可以完全由第三方扩展。 Pomelo的框架组成 pomelo包括三部分: 框架, pomelo的核心, 与以往单进程的游戏框架不同, 它是高性能、分布式的游戏服务器框架,并且使用很简单 库, 包括了开发游戏的常用工具库, 如人工智能(ai), 寻路, aoi等 工具包, 包括管理控制台, 命令行工具, 压力测试工具等 pomelo特性 快速、易上手的游戏开发模型和api 高可伸缩的多进程架构, 支持MMO的场景分区和其它各类分区策略 方便的服务器扩展机制,可快速扩展服务器类型和数量 方便的请求、响应、广播、服务器通讯机制, 无需任何配置 注重性能,在性能、可伸缩性上做了大量的测试、优化 提供了较多扩展组件,包括游戏开发常用的库和工具包 提供了完整的MMO demo代码(客户端html5),可以作为很好的开发参考 基于socket.io开发,支持socket.io支持的多种语言客户端 为什么使用pomelo? 高并发、高实时的游戏服务器的开发是很复杂的工作。跟web应用一样, 一个好的开源容器或开发框架可以大大减少游戏开发的复杂性,让开发变得更加容易。遗憾的是目前在游戏服务器开发领域一直没有太好的开源解决方案。 pomelo将填补这个空白, 打造一款完全开源的高性能(并发)游戏服务器框架。 pomelo的优势有以下几点: 架构的可伸缩性好。 采用多进程单线程的运行架构,扩展服务器非常方便, node.js的网络io优势提供了高可伸缩性。 使用非常容易, 开发模型与web应用的开发类似,基于convention over configuration的理念, 几乎零配置, api的设计也很精简, 很容易上手。 框架的松耦合和可扩展性好, 遵循node.js微模块的原则, framework本身只有很少的代码,所有component、库、工具都可以用npm module的形式扩展进来。任何第三方都可以根据自己的需要开发自定义module。 提供完整的开源MMO游戏demo参考(基于HTML 5)。 一个超过1万行代码的游戏demo,使开发者可以随时借鉴demo的设计与开发思路。 在线演示:http://pomelo.netease.com/demo.html 标签:开发框架  游戏框架
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值