• Pomelo分布式游戏服务器框架


    Pomelo介绍&入门

    目录

    前言&介绍

    Pomelo:一个快速、可扩展、Node.js分布式游戏服务器框架

    从三四年前接触Node.js开始就接触到了Pomelo,从Pomelo最初的版本到现在,总的来说网易出品还算不错,但是发展不算快;用它做过一些项目和小游戏表现还不错。

    用它的主要好处:

    1. 入门简单,有比较丰富的文档和示例(虽然现在看版本也比较老了,但是入门没什么问题)

    2.分布式多进程且扩展简单(单进程多线程,每个服务器都是一个Node进程,通过配置文件就可以管理集群)

    3.可以不去关注底层和网络相关逻辑,聚焦业务逻辑的处理,对于有Web服务器开发经验却没有游戏服务器开发经验来说还是比较友好的

    4.提供了很多工具和客户端支持(像IOS、Android & Java、Javascript、C、Cocos2d-x、U3D等)

    ......  

      入门参考链接

      https://github.com/NetEase/pomelo/wiki/Home-in-Chinese

      其它链接:  

      https://github.com/NetEase/pomelo

      https://www.npmjs.com/package/pomelo

    安装Pomelo

      安装要求  

    Windows下安装要求环境

    Python (2.5 < 版本 < 3)

    VC++编译器

    PS:  Windows新环境自已检查一下,我本机环境已经装好python2.7,Visaul Studio也安装了所以也有VC++编译器

      其它操作系统应该问题不大

    官方安装介绍文档:https://github.com/NetEase/pomelo/wiki/%E5%AE%89%E8%A3%85pomelo

        全局安装Pomelo

    npm install pomelo -g

      安装成功后如下图,可以看到现在最新版本为2.2.5

     

     说明:Pomelo光是安装可能出现各种失败

        1. 回头去检查一下,Python和VC++编辑器是否有问题

        2.如果以前全局安装过Pomelo,最好删除掉 “C:Users当前用户AppDataRoaming pm ode_modules”目录下Pomelo文件夹和“C:Users当前用户AppDataRoaming pm-cache”目录下Pomelo开头的文件夹

        3.如果并不报错,npm卡住不动,多数是网络原因,重复多安几次;或者打开FQ工具试试;也可以用淘宝镜像 cnpm 安装

    创建项目并启动

     安装好pomelo之后,开始创建项目并安装依赖项

    pomelo init 项目名

     执行创建项目命令后,出现如下图选择项(Please select underly connector, 1 for websocket(native socket), 2 for socket.io, 3 for wss, 4 for socket.io(wss), 5 for udp, 6 for mqtt: [1]

      这是让你选择connector的协议,除了5 for udp,其它都是长连接,我们接下来选择 2 for socket.io

      在上图cmd中输入2,并回车,选择socket.io继续安装

      这里connector协议可以通过app.js配置进行修改// app configuration

    复制代码
    app.configure('production|development', 'connector', function(){
      app.set('connectorConfig',
        {
          connector : pomelo.connectors.sioconnector,
        ...
    }); });
    复制代码

      成功后,转到项目根目录,执行安装项目执行 npm-install.bat 依赖项 (其它平台执行npm-install.sh)

    cd 项目目录
    npm-install.bat

     项目创建完成后,目录如下图

      项目结构说明

      game-server :  游戏服务器,所有游戏服务器功能和逻辑都在此目录下

        game-server/app.js:入口文件

        game-server/app: 存放游戏逻辑和功能相关代码都这个子目录下,servers目录下可以新建多个目录来创建不同类型的服务器,在pomelo中,使用路径来区分服务器类型

        game-server/config:存放游戏服务器配置文件目录,像日志、服务器、数据库等几乎所有配置文件都可以存放到此目录下

        game-server/logs:日志目录,存放游戏服务器所有日志文件

      web-server:  web服务器(如果你是个H5游戏,这里就是Web客户端,如果是IOS、Andriod客户端,这目录就没什么用)

      shared:公共代码存放处,这里要以放一些共用代码

     所有依赖项安装成功后,开始启动项目

       启动game-server

    cd game-server
    pomelo start

        启动命令执行成功后,出现如下图错误提示

    [2017-11-23 11:54:42.226] [ERROR] console - Option path is not valid. Please refer to the README.
    [2017-11-23 11:54:42.226] [ERROR] console - Option close timeout is not valid. Please refer to the README.
    [2017-11-23 11:54:42.226] [ERROR] console - Option heartbeats is not valid. Please refer to the README.
    [2017-11-23 11:54:42.226] [ERROR] console - Option log level is not valid. Please refer to the README.

      问题原因和解决方式

      原因:新版的socket.io用法不正确的导致的,官方早已修复,就是没有publish到npm包中

      修复方式:把node_modules目录下的pomelo中sioconnector.js(../game-server/node_modules/pomelo/lib/connectors/sioconnector.js

                       替换为 https://github.com/NetEase/pomelo/blob/master/lib/connectors/sioconnector.js

     替换后再启动game-server,就没有这些错误提示了^_^!

     测试连接

      1.启动web-server

    cd web-server
    node app

     启动后如下图

      会发些有一些提示,这是express写法问题,可以打开web-server根目录下app.js,按如下修改

    //var app = express.createServer();  注释掉这一行代码,替换为下面这一行代码
    var app = express();

     再启动时无express用法提示^_^!

     2.打开http://localhost:3001

     如上图,点击“Test Game Server”按钮,提示“game server is ok.”,则此项目game-server可用^_^!

    聊天服务器

     上面大体了解了pomelo,要入门还是以一个聊天服务器为入门示例最好,其它逻辑相对简单,入门学习不会因其它游戏逻辑影响。

     官方有个非常好的示例:https://github.com/NetEase/chatofpomelo  官方也有很多说明

     网上也有很多文章分析讲解这项目,我就不完全解释些项目了,接下来我就在上面新建的好的“PomeloDemo”的基础上改成一个聊天服务器

     1.新建gate和chat服务器

      在app/servers目录下新建gate和chat服务器,新建好后目录如下

     

     gate服务器:

     在一般情况下用户量一台机器就可以支撑,但用户量多了就得扩充服务器,gate服务器的作用就相当于前端负载均衡服务器;

     客户端向gate服务器发出请求,gate服务器会给客户端分配一个connector服务器;

     分配策略是根据客户端的某一个key做hash得到connector的id,这样就可以实现各个connector服务器的负载均衡。这个一会儿会实现

     connector服务器: 

     接受客户端请求,并将其路由到chat服务器,以及维护客户端的链接;

     同时,接收客户端对后端服务器的请求,按照用户配置的路由策略,将请求路由给具体的后端服务器。当后端服务器处理完请求或者需要给客户端推送消息的时候,connector服务器同样会扮演一个中间角色,完成对客户端的消息发送;

     connector服务器会同时拥有clientPort和port,其中clientPort用来监听客户端的连接,port端口用来给后端提供服务;

     chat服务器:

     handler和remote决定了服务器的行为;

     handler接收用户发送过来的send请求,remote由connector RPC发起远程调用时调用;

     在remote里由于涉及到用户的加入和退出,所以会有对channel的操作。

     其实也可以提前了解一些Pomelo中的术语,不分别解释,可以提前看看:https://github.com/NetEase/pomelo/wiki/%E6%9C%AF%E8%AF%AD%E8%A7%A3%E9%87%8A

     2.配置master.json

     master.json

      3.配置servers.json

      打开config目录下servers.json文件,配置好各种 type 的服务器,配置如下

     servers.json

      解释一下配置中的各字段:

      id:   字符串类型的应用服务器ID

      host:应用服务器的IP或者域名

      port:RPC请求监听的端口

      clientPort: 前端服务器的客户端请求的监听端口

      frontend:bool类型,是否是前端服务器,默认: false

      可选参数:

      max-connections:前端服务器最大客户连接数

      args: node/v8配置,如配置为"args": "--debug=5858 "这样就可以启用项目调试(没用过,临时问了一下谷歌,看别人是这么解释的^_^!)

     4.配置adminServer.json

     打开config目录下adminServer.json文件,配置好各种 type 的服务器,配置如下

     adminServer.json

     它有什么作用,可以看一下以下2个链接

     http://blog.csdn.net/nynyvkhhiiii/article/details/49249915

     https://github.com/NetEase/pomelo-admin#server-master-auth

     5.解决服务器分配问题

     从上面的servers.json配置的修改可以看出与最开始创建出来的项目一个服务器相比,connector和chat我都配置了三个服务器

     这就要解决客户端请求服务器分配问题

     解决思路:用户访问gate服务器,使用用户的uid的crc32的校验码与connector服务器的个数取余,从而得到一个connector服务器,把这个connector服务器分配给请求用户

     在app目录下新建util目录,目录下新建“dispatcher.js”和 “routeUtil.js”文件,处理此服务器分配逻辑

     dispatcher.js
     routeUtil.js

      准备好这些文件后,在game-server服务器入口文件app.js中添加配配置

    复制代码
    var pomelo = require('pomelo');
    var routeUtil = require('./app/util/routeUtil');
    
    /**
     * Init app for client.
     */
    var app = pomelo.createApp();
    app.set('name', 'PomeloDemo');
    
    // app configuration
    // app.configure('production|development', 'connector', function(){
    app.configure('production|development', function(){
      // route configures
      app.route('chat', routeUtil.chat);
      app.set('connectorConfig',
      {
          connector : pomelo.connectors.sioconnector,
          // 'websocket', 'polling-xhr', 'polling-jsonp', 'polling'
          transports : ['websocket', 'polling'],
          heartbeats : true,
          closeTimeout : 60 * 1000,
          heartbeatTimeout : 60 * 1000,
          heartbeatInterval : 25 * 1000
      });
      // filter configures
      app.filter(pomelo.timeout());
    });
    
    // start app
    app.start();
    
    process.on('uncaughtException', function (err) {
      console.error(' Caught exception: ' + err.stack);
    });
    复制代码
     
      app.filter(pomelo.timeout());
      
      过滤器,pomelo内置了一些过滤器,可以自行去了解一下,也可以根据自已的需求去自定义!
      

     注意:

       app.configure('production|development', 'connector', function(){   

       修改为

       app.configure('production|development',  function(){

       这个如果不修改,在启动调用时会遇到 engine.io 中报错  TypeError: Cannot read property  'indexOf' of undefined  at Server.verify !

     6.实现 gate.gateHandler.queryEntry

      作用:用户连接gate服务器,返回分配的connector

      在gate目录下handler下新建gateHandler.js,代码如下

     gateHandler.js

     7.实现chat服务器chatRemote.js 

     chat服务器会接受connector的远程调用,完成channel维护中的用户的加入以及离开

     chatRemote.js

     可以看到上面代码中的add和kick分别对应着加入和离开channel

     8.实现chat服务器chatHandler.js

     chat服务器执行聊天逻辑,维护channel信息,一个房间就是一个channel,一个channel里有多个用户,当有用户发起聊天的时候,就会将其内容广播到整个channel。

     chatHandler.js

     这里面是发送消息(给房间内所有人和指定用户)

     9.实现connector中entryHandler.js

      主要完成接受客户端的请求,维护与客户端的连接,路由客户端的请求到chat服务器;

     entryHandler.js

      这里完成的主要就是RPC远程调用chat服务器chatRemote中的实现

    10.运行

      到此这个聊天服务器实现就完成, 打开命令行工具,执行没有错误信息,基本就成功了!

    cd game-server目录
    pomelo start

    编写web聊天客户端测试

      我就在web-server目录中写了个测试客户端

      把结构改了一下,换成了ejs模版,代码如下

      routes中index.js文件代码

     index.js

     views中index.ejs文件代码

    复制代码
    <html>
    <head><title><%= title %></title></head>
    <body>
        <div id="tipMsg" style="color:red; height:30px;"></div>
        <div id="pnlLogin">    
        <h1>1.登录(连接Gate服务器)</h1>
        用户名:<input id="txtUserName" type="text" maxlength="20" ></br>
        房间号:<input id="txtRoomId" type="text" maxlength="8" >
        <input id="btnLogin" type="button" value="点击登录" />
        <br/>
        </div>    
        <div id="pnlChat" style="display:none;">
        <h1>2.聊天室</h1>    
        用户名:<span id="spUserName" style="color:blue;padding-right:50px;"></span>
        房间号:<span id="spRoomId" style="color:blue;padding-right:50px;"></span>    
        <div id="txtMessage" style="800px; height:400px;border:1px solid #000; overflow-y:auto; overflow-x:hidden; "></div>
        <br/>
        发送给:<select id="selUserList" style="200px;">
                    <option value="*">所有人</option>
               </select>
               <br/>
               <br/>
               <textarea id="txtSendMessage" type="text" style="690px;height:80px; overflow:auto;float:left;" ></textarea>
               <input id="btnSend" type="button" value="发送" style="margin-left:10px; height:80px; 100px;float:left;" />
        </div>
    </body>
    </html>
    <script src="js/socket.io.js"></script>
    <script src="js/pomeloclient.js"></script>
    <script src="js/jquery-1.11.2.min.js"></script>
    <script type="text/javascript">
    
    $(function(){
    
        var rid = '';
        var uname = '';
    
        //监听"onAdd", 当有新用户加入时触发
        pomelo.on('onAdd', function(data) {
            var user = data.user;
            $('#txtMessage').append('<span style="color:green;">[上线提醒]:欢迎 ' + user + ' 加入聊天室<span><br/>');
    
            //添加到用户列表
            $('#selUserList').append('<option value="' + user + '">' + user + '</option>');
        });
    
        //监听"onLeave", 当有用户离开聊天室时触发
        pomelo.on('onLeave', function(data) {
            var user = data.user;
            $('#txtMessage').append('<span style="color:green;">[离线提醒]: ' + user + ' 离开聊天室<span><br/>');
    
             //从用户列表移除
            $('#selUserList option[value="' + user + '"]').remove();
        });
    
        // 监听"onChat", 接收消息
        pomelo.on('onChat', function(data) {
            var from = data.from,
                target = data.target,
                msg = data.msg;            
                    
            if(msg === null) return;
    
            var name = (target == '*' ? '所有人' : target);
            var time = getNowFormatDate();        
            
            $("#txtMessage").append('<span style="color:blue;">[' + time +'][' + from + '] 对 [' +  name + '] 说: ' + msg + '<span><br/>');
        });
    
        //当从聊天断开时
        pomelo.on('disconnect', function(reason) {        
            $('#pnlLogin').show();
            $('#pnlChat').hide();                
        });
    
        //登录
        $('#btnLogin').on('click', function(){
            var userNameReg = /^[a-zA-Z0-9]+$/,
                roomIdReg = /^[0-9]+$/;
    
                uname = $.trim($('#txtUserName').val()),
                rid = $.trim($('#txtRoomId').val());
    
            if(uname.length == 0){
                alert('请输入登录名');
                return false;
            }
    
            if(!userNameReg.test(uname)) {
                alert('登录名只能由字母或数字组成');
                return false;
            }
    
            if(rid.length == 0){
                alert('请输入房间号');
                return false;
            }
    
            if(!roomIdReg.test(rid)) {
                alert('房间号只能是数字');
                return false;
            }
            
            queryEntry(uname, function(host, port){
                $('#tipMsg').append('Gate 连接成功! host:' + host + '  port:' + port + '<br/>');
                
                //连接聊天服务器
                pomelo.init({
                    host: host,
                    port: port,
                    log: true
                }, function() {
                    var route = "connector.entryHandler.enter";
                    pomelo.request(route, {
                        username: uname,
                        rid: rid
                    }, function(data) {
                        if(data.error) {
                            $('#tipMsg').append('Chat 连接失败!<br/>');
                            return;
                        }            
    
                        $('#tipMsg').append('Chat 连接成功!<br/>');
    
                        $('#pnlLogin').hide();
                        $('#pnlChat').show();
    
                        $('#spUserName').text(uname);
                        $('#spRoomId').text(rid);
    
                        //加载当前聊天室 已在线用户列表
                        $.each(data.users, function(i, item){                     
                            if(item != uname){
                                $('#selUserList').append('<option value="' + item + '">' + item + '</option>');
                            }                        
                        }); 
    
                    });
                });
            });
        });
    
        //发送消息
        $('#btnSend').on('click', function(){
            var route = "chat.chatHandler.send",
                target = $("#selUserList").val(),
                msg = $.trim($("#txtSendMessage").val());            
    
            if(msg.length == 0){
                alert('不能发送空消息!');
                return;
            }
    
            pomelo.request(route, {
                rid: rid,
                content: msg,
                from: uname,
                target: target
            }, function(data) {
                
                $("#txtSendMessage").val('');
    
                if(from == uname) {
                    var name = (target == '*' ? '所有人' : target);
                    var time = getNowFormatDate();        
            
                    $("#txtMessage").append('<span style="color:blue;">[' + time +'][' + from + '] 对 [' +  name + '] 说: ' + msg + '<span><br/>');
                }
    
            });
            
        });
         
    })
    
    //连接Gate服务器
    function queryEntry(uid, callback) {
        var route = 'gate.gateHandler.queryEntry';
        pomelo.init({
            host: '127.0.0.1',
            port: 15014,
            log: true
        }, function() {    
            pomelo.request(route, {
                uid: uid
            }, function(data) {
                pomelo.disconnect();
                if(data.code === 500) {                
                    alert('用户名在此房间中已存在,请重新输入新的用户名!');
                    return;
                }
                callback(data.host, data.port);
            });
        });
    };
    
    function getNowFormatDate() {
        var date = new Date();
        var seperator1 = "-";
        var seperator2 = ":";
        var month = date.getMonth() + 1;
        var strDate = date.getDate();
        if (month >= 1 && month <= 9) {
            month = "0" + month;
        }
        if (strDate >= 0 && strDate <= 9) {
            strDate = "0" + strDate;
        }
        var currentdate = date.getFullYear() + seperator1 + month + seperator1 + strDate
                + " " + date.getHours() + seperator2 + date.getMinutes()
                + seperator2 + date.getSeconds();
        return currentdate;
    }
    
    </script>
    复制代码

       app.js代码如下:

     app.js

     运行起来后,测试结果如下图:

    写在之后

       Pomelo学习入门不算复杂,写一篇感觉讲不全,写多篇感觉太散,大家将就着看,有些东西不认识的还是去看一下API文档 http://pomelo.netease.com/api.html  (也是低水准的官方API^_^!)

       或者问一下google啥的... 

       可以参考这两个例子来学习:

       https://github.com/NetEase/chatofpomelo

       https://github.com/NetEase/lordofpomelo

       入门建议从chatofpomelo开始

       看之前可以提前看看一些pomelo术语,有个大体了解,再边看代码边理解:https://github.com/NetEase/pomelo/wiki/%E6%9C%AF%E8%AF%AD%E8%A7%A3%E9%87%8A

       主要参考资料:

       https://github.com/NetEase/pomelo/wiki/Home-in-Chinese  (比较杂乱,可能官方大神都忙着搞赚钱的项目,将就着看,有很多东西对入门来说还是很有用的)

       

       如果有些问题解决不了,可以去社区问一下:http://nodejs.netease.com/tag/pomelo  (感觉现在活跃度也比较低^_^!)

     

    作   者:   Porschev[钟慰] 
    出   处:   http://www.cnblogs.com/zhongweiv/ 
    微   博:     http://weibo.com/porschev 

  • 相关阅读:
    【Android 界面效果23】LayoutInflater作用及使用
    【Android Api 翻译4】android api 完整翻译之Contacts Provider (学习安卓必知的api,中英文对照)
    【Android Api 翻译3】android api 完整翻译之Application Fundamentals (学习android必须知道的)
    【Mood-7】tell 2 my gf-miss u not sudden but always
    Android权限机制
    【Mood-6】空气显示触摸屏、智能钱夹
    【Android 界面效果22】Android的Tab与TabHost
    【Android 界面效果21】Android ViewPager使用详解
    【Android 界面效果20】Android GradientDrawable类的详解,设置activity的背景颜色渐变效果
    【Android 界面效果19】Android中shape的使用
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/7967951.html
Copyright © 2020-2023  润新知