pomelo + vscode + typescript搭建可约束可调试的游戏服务端框架

  • Post author:
  • Post category:其他


说在前面

pomelo: 它是网易开源的一套基于Node.js的游戏服务端框架,详情

请戳这里

关于pomelo的种种这里不详细说。点击链接查看详情。但是由于pomelo是js项目,使用起来的时候并不是很爽,所以就有了ts的引入。

typescript: TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。换而言之,TypeScript是在JavaScript的基础上添加了语法糖,使其在使用的过程中完成了语法和类型的检测,同时达到更便利的代码提示。本质上typeScript在运行前是需要通过tsc编译成Javascript,然后运行。所以TS的最大便利是在使用的过程,而本质还是JS。对于TS的更多相关信息,自行脑补。

vscode: Visual Studio Code (简称 VS Code / VSC) 是一款免费开源的现代化轻量级代码编辑器,支持几乎所有主流的开发语言的语法高亮、智能代码补全、自定义热键、括号匹配、代码片段、代码对比 Diff、GIT 等特性,支持插件扩展,并针对网页开发和云端应用开发做了优化。软件跨平台支持 Win、Mac 以及 Linux。

VSC中文网

,用过的都知道它的好~~~

所以前面说了这么多,其实就是在讲,想这么干的往下看。

流程

环境安装


  1. Node.js

  2. vscode

  3. pomelo

  4. typescript
  5. 安装VSCode扩展插件:ESLint, TSLint.

新建一个Pomelo项目

  1. 新建一个文件夹
  2. 进入该文件夹

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    $ pomelo init
    
    The default admin user is:
    
      username: admin
      password: admin
    
    You can configure admin users by editing adminUser.json later.
    
    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]
    
    
    $ 1
    
        create : E:pomelotestnpm-install.bat
        create : E:pomelotestnpm-install.sh
        create : E:pomelotestweb-server
        create : E:pomelotestshared
    #... 省略部分输出
        create : E:pomeloTestweb-serverpublicjsliblocalboot
        create : E:pomeloTestweb-serverpublicjsliblocalbootcomponent.json
        create : E:pomeloTestweb-serverpublicjsliblocalbootindex.js
    
  3. windows用户执行项目文件夹下的 “npm-install.bat”, shell 用户执行文件夹下的 “npm-install.sh” 进行依赖安装,初始化pomelo项目.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
E:pomelotest>npm-install.bat
npm info it worked if it ends with ok
npm info using npm@5.6.0
npm info using node@v8.11.1
#... 省略部分输出

npm notice created a lockfile as package-lock.json. You should commit this file.
npm info lifecycle undefined~postshrinkwrap: undefined
npm WARN Test@0.0.1 No description
npm WARN Test@0.0.1 No repository field.
npm WARN Test@0.0.1 No license field.
#... 省略部分输出
added 29 packages in 3.713s
npm info lifecycle undefined~postshrinkwrap: undefined
npm WARN Test@0.0.1 No description
npm WARN Test@0.0.1 No repository field.
npm WARN Test@0.0.1 No license field.

added 312 packages in 29.82s
npm info ok

E:pomelotestgame-server>

至此pomelo项目创建成功。

接下来测试一下项目是否正确可行。

  1. 试运行pomelo项目

    a. 进入到项目目录下的game-server文件夹执行app.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    $ cd game-server
    $ node app
    
    [2018-06-20 22:05:45.215] [INFO] pomelo - [E:pomeloTestgame-servernode_modulespomelolibapplication.js] application inited: "master-server-1"
    [2018-06-20 22:05:45.325] [INFO] pomelo-admin - [MqttServer] [MqttServer] listen on 3005
    [2018-06-20 22:05:45.328] [INFO] pomelo - [E:pomeloTestgame-servernode_modulespomelolibmasterstarter.js] Executing D:Program Filesnodejsnode.exe E:pomeloTestgame-serverapp,env=development,id=connector-server-1,host=127.0.0.1,port=3150,clientHost=127.0.0.1,clientPort=3010,frontend=true,serverType=connector locally
    [2018-06-20 22:05:45.332] [INFO] pomelo-admin - [ConsoleService] try to connect master: "master", "127.0.0.1", 3005
    [2018-06-20 22:05:45.372] [DEBUG] pomelo - [E:pomeloTestgame-servernode_modulespomelolibmodulesmasterwatcher.js] masterwatcher receive add server 
    #... 省略部分输出
    [2018-06-20 22:05:45.715] [INFO] pomelo - [E:pomeloTestgame-servernode_modulespomelolibmasterwatchdog.js] all servers startup in 485 ms
    

看到 all servers startup in 485 ms 了没有,说明服务器启动成功了。

b. 有服务器肯定会有前端,前端就是项目根目录下的web-server文件夹,了解一下。进入到web-server文件夹,启动前端服务。

新建一个控制台

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ cd web-server
$ node app

Warning: express.createServer() is deprecated, express
applications no longer inherit from http.Server,
please use:

  var express = require("express");
  var app = express();

connect.multipart() will be removed in connect 3.0
visit https://github.com/senchalabs/connect/wiki/Connect-3.0 for alternatives
connect.limit() will be removed in connect 3.0
Web server has started.
Please log on http://127.0.0.1:3001/index.html

其中的警告是说:’express.createServer()’ 方法已经废弃,请使用下面的‘ var express = require(“express”); var app = express();’这是因为项目的模板比较旧没有进行更新,而新版本的express已经废弃了该方法,但是目前还不影响使用,先不管它。

好了,前端也跑起来了,输入链接测试:

http://127.0.0.1:3001/index.html


看到下图:

表示前端启动成功了。

点击”Test Game Server”按钮,测试后端。

看到下图:

表示服务端也启动成功。

如果出现启动失败请查看输出,自行百度解决,一般是由于模块没有安装完整,或者端口被占用的问题。如果又要请留言。

然后开始第二阶段,项目改造,接入TS。

改造项目为TypeScript

  1. 用VSCode打开 game-server 项目
  2. 在game-server目录下新建一个app_types文件夹。
  3. 在文件夹内添加ts配置文件“tsconfig.json”, 文件内容如下。
1
2
3
4
5
6
7
8
9
10
11
{
  "compilerOptions": {
    "module": "commonjs",
    "lib": [ "dom", "es6", "es2015.promise" ],
    "target": "es6",
    "experimentalDecorators": true,
    "skipLibCheck": true,
    "outDir": "../app",
    "sourceMap": true,
  },
}

解释下ts的这个配置:

“compilerOptions”: 编译选项

“module”: 指定生成哪个模块系统代码: “None”, “CommonJS”, “AMD”, “System”, “UMD”, “ES6”或 “ES2015”。

“lib”: 编译过程中需要引入的库文件的列表。 可能的值为: “ ES5 “,” ES6 “,” ES2015 “,” ES7 “,” ES2016 “,” ES2017” 等;

“target”: 指定ECMAScript目标版本 “ES3”(默认), “ES5”, “ES6”/ “ES2015”, “ES2016”, “ES2017”或 “ESNext”。

“experimentalDecorators”: 启用实验性的ES装饰器。(这里可以不加)

“skipLibCheck”: 忽略所有的声明文件( *.d.ts)的类型检查。

“outDir”: 重定向输出目录。

“sourceMap”:生成相应的 .map文件。

其他的配置的详情请

戳这里

  1. 修改代码

    我们可以先看下原来app中的代码结构:

    –|app

    –|servers

    --|connector
        --|handler
            --entryHandler.js
    

    这个结构是不可以乱改的,至于为啥请戳上面pomelo官网翻阅文档。

我们需要做的是,当我们写好ts的代码之后,编译之后的ts代码需要输出到app目录下,并且保证servers下的目录和pomelo的要求一直。当pomelo启动,读取服务器配置”config/server.json”时,能找到对应的js代码来启动服务。

我们可以看一下server.json中的内容:

1
2
3
4
5
6
7
8
9
10
11
12
{
  "development":{
    "connector": [
    {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientHost": "127.0.0.1", "clientPort": 3010, "frontend": true}
    ]
  },
  "production":{
    "connector": [
    {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientHost": "127.0.0.1", "clientPort": 3010, "frontend": true}
    ]
  }
}

其中的 “connector” 表示服务型所在的目录,也就是’app/servers/connector’.所以这也是为什么需要保证app下目录的完整性。

所以实际操作如下:

a. 在app_types目中中创建如下文件夹结构:

–|app_types

–|servers

–|connector

–|handler

b. 在handler文件夹中新建ts文件:”entryHandler.ts”,填充如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class ConnectorHandler {

    protected app;

    constructor(app) {
        this.app = app;
    }

    public entry(msg, session, next) {
        next(null, {code: 200, msg: 'game server is ok.'});
    }

    public publish(msg, session, next) {
        var result = {
            topic: 'publish',
            payload: JSON.stringify({code: 200, msg: 'publish message is ok.'})
        };
      next(null, result);
    }

    public subscribe(msg, session, next) {
        var result = {
            topic: 'subscribe',
            payload: JSON.stringify({code: 200, msg: 'subscribe message is ok.'})
        };
      next(null, result);
    }
}

export = function(app) {
    return new ConnectorHandler(app);
}

c.添加ts编译任务:

在VSCode下,按下快捷键Ctrl+Shift+P打开任务面板,如图所示:


选择“任务配置”(tasks: configure task);

然后选择构tsc: “构建- app_types/tsconfig.json”;

这时候会在目录下创建一个.vscode文件夹,文件夹中有一个tasks.json的配置文件。

task中的内容为下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "type": "typescript",
            "tsconfig": "app_types\tsconfig.json",
            "problemMatcher": [
                "$tsc"
            ]
        }
    ]
}

d. 配置完成之后,检测一下构建结果是否如你所示,首先删除app文件夹中所有内容;

然后构建:Ctrl+Shift+B,

在弹出的面板中选择构建: app_typestsconfig.json

这是后可以看到,下方的面板中显示如下:


这时候查看下app中的文件夹,结构如下:


这时候可以发现和我们原先设想的不一样啊。。。按理说这个entryHandler.js应该在app/servers/connector/handler下才对。

但是折腾了好久也没照道具体的原因和配置。但是还是有解决的方法,如果有知道的道友希望您能告知下应该这么处理,我的解决方法是:

在app_types中新建一个文件夹,我叫做util,然后在util中放入一个ts脚本。然后写入一些代码。再编译(编译前请删除app中的旧的文件)。

这时候如图所示:


这样就达到我们预期的效果。

我们来看一下编译之后的js代码和原来的js代码的区别;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 这是编译的代码es6的版本
;
class  {
    constructor(app) {
        this.app = app;
    }
    entry(msg, session, next) {
        next(null, { code: 200, msg: 'game server is ok.' });
    }
    publish(msg, session, next) {
        var result = {
            topic: 'publish',
            payload: JSON.stringify({ code: 200, msg: 'publish message is ok.' })
        };
        next(null, result);
    }
    subscribe(msg, session, next) {
        var result = {
            topic: 'subscribe',
            payload: JSON.stringify({ code: 200, msg: 'subscribe message is ok.' })
        };
        next(null, result);
    }
}
module.exports = function (app) {
    return new ConnectorHandler(app);
};
//# sourceMappingURL=entryHandler.js.map
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 这是原来的代码
module.exports = function(app) {
  return new Handler(app);
};

var Handler = function(app) {
  this.app = app;
};

Handler.prototype.entry = function(msg, session, next) {
  next(null, {code: 200, msg: 'game server is ok.'});
};

Handler.prototype.publish = function(msg, session, next) {
	var result = {
		topic: 'publish',
		payload: JSON.stringify({code: 200, msg: 'publish message is ok.'})
	};
  next(null, result);
};

Handler.prototype.subscribe = function(msg, session, next) {
	var result = {
		topic: 'subscribe',
		payload: JSON.stringify({code: 200, msg: 'subscribe message is ok.'})
	};
  next(null, result);
};

看起来差很多对吧,但是如果改成es5的话呢?

修改tsconfig.json内容:

1
2
3
4
5
6
7
8
9
10
11
{
    "compilerOptions": {
      "module": "commonjs",
      "lib": [ "dom", "es5", "es2015.promise" ],
      "target": "es5",
      "experimentalDecorators": true,
      "skipLibCheck": true,
      "outDir": "../app",
      "sourceMap": true,
    },
  }

然后重新编译代码,这是js脚本的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
;
var ConnectorHandler = /** @class */ (function () {
    function (app) {
        this.app = app;
    }
    ConnectorHandler.prototype.entry = function (msg, session, next) {
        next(null, { code: 200, msg: 'game server is ok.' });
    };
    ConnectorHandler.prototype.publish = function (msg, session, next) {
        var result = {
            topic: 'publish',
            payload: JSON.stringify({ code: 200, msg: 'publish message is ok.' })
        };
        next(null, result);
    };
    ConnectorHandler.prototype.subscribe = function (msg, session, next) {
        var result = {
            topic: 'subscribe',
            payload: JSON.stringify({ code: 200, msg: 'subscribe message is ok.' })
        };
        next(null, result);
    };
    return ConnectorHandler;
}());
module.exports = function (app) {
    return new ConnectorHandler(app);
};
//# sourceMappingURL=entryHandler.js.map

这时候发现是不是差不多了,这是因为es6引入了类的概念,不细说,自行脑补啦,反正俩种都可以运行的。

e. 测试成果的时候到了,分别跑服务端和客户端,看看效果是不是和原来一样。

在终端输如

1
2
3
4
5
6
7
8
9
$ pomelo start
# ...
[2018-06-21 00:19:21.643] [INFO] pomelo - [E:pomeloTestgame-servernode_modulespomelolibapplication.js] "connector-server-1" enter after start...

[2018-06-21 00:19:21.645] [INFO] pomelo - [E:pomeloTestgame-servernode_modulespomelolibapplication.js] "connector-server-1" finish start

[2018-06-21 00:19:21.645] [INFO] pomelo - [E:pomeloTestgame-servernode_modulespomelolibapplication.js] "connector-server-1" startup in 206 ms

[2018-06-21 00:19:21.646] [INFO] pomelo - [E:pomeloTestgame-servernode_modulespomelolibmasterwatchdog.js] all servers startup in 513 ms

如果和原来一样),说明你的改造是成功的了。这是后可以尝试下启动前端,在测试一下,一般是OK的。

所以接下来时调试了。

pomelo ts 版本的调试

PS: 有人说,打LOG是最好的调试~~

本教程采用远程调试的方式。

1.首先查看Node的调试参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ node -h

Usage: node [options] [ -e script | script.js | - ] [arguments]
       node inspect script.js [arguments]

Options:
  -v, --version              print Node.js version
  -e, --eval script          evaluate script
  -p, --print                evaluate script and print result
  -c, --check                syntax check script without executing
  -i, --interactive          always enter the REPL even if stdin
                             does not appear to be a terminal
  -r, --require              module to preload (option can be repeated)
  -                          script read from stdin (default; interactive mode if a tty)
  # 这个就是我们需要的调试参数(别问我怎么知道是这个的,因为有个东西叫网络~~)
  --inspect[=[host:]port]    activate inspector on host:port
                             (default: 127.0.0.1:9229)
  --inspect-brk[=[host:]port]
                             activate inspector on host:port
                             and break at start of user script
  --inspect-port=[host:]port
                             set host:port for inspector
  --no-deprecation           silence deprecation warnings
  --trace-deprecation        show stack traces on deprecations
  --throw-deprecation        throw an exception on deprecations
  1. 找到pomelo项目中的config/server.json. 内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    {
      "development":{
        "connector": [
        {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientHost": "127.0.0.1", "clientPort": 3010, "frontend": true}
        ]
      },
      "production":{
        "connector": [
        {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientHost": "127.0.0.1", "clientPort": 3010, "frontend": true}
        ]
      }
    }
    

因为pomelo每一个服务器都是一个进程,所以调试的时候只能选择其中一个进行调试。虽然比较蛋疼,但是用习惯了还是好的。

pomelo在启动服务器的时候会将这些参数传递给Node…应该是这样的,嗯,应该是…

修改 “connector”服务器的配置,添加调试参数:

1
"args": " --inspect=127.0.0.1:16772"

所以如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
{
  "development":{
    "connector": [
    {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientHost": "127.0.0.1", "clientPort": 3010, "frontend": true, "args": " --inspect=127.0.0.1:16772"}
    ]
  },
  "production":{
    "connector": [
    {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientHost": "127.0.0.1", "clientPort": 3010, "frontend": true}
    ]
  }
}

这个表示我们队服务器connector开启了远程调试功能。

  1. 然后配置VSCode调试参数:

    如图所示:

    先选择侧边栏的调试按钮,然后选择上方的配置按钮,在弹出的面板中选择Node.js, 添加launch.json

    修改配置如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    {
        // 使用 IntelliSense 了解相关属性。 
        // 悬停以查看现有属性的描述。
        // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
        "version": "0.2.0",
        "configurations": [
            {
                "type": "node", // 调试的类型
                "name": "pomelo remote", // 名称
                "port": 16772, // 监听的端口(配置在server.json中的调试端口)
                "sourceMaps": true, // 开启源映射
                "outFiles": [ // js脚本目录
                    "${workspaceRoot}/app",
                ],
                
                "request": "attach", // 请求配置的类型,启动或者附加,我们用的是附加
            }
        ]
    }
    

OK, 配置完成保存,然后开始开开心心的测试一下。

  1. 测试:

    a. 启动game-server

    1
    2
    3
    4
    5
    6
    
    $ pomelo start
    # ....
    Debugger listening on ws://127.0.0.1:16772/202b2f5e-3671-4fce-aca9-d2cdf175bc69
    For help see https://nodejs.org/en/docs/inspector
    # ...
    [2018-06-22 01:01:19.776] [INFO] pomelo - [E:pomeloTestgame-servernode_modulespomelolibmasterwatchdog.js] all servers startup in 884 ms
    

如果终端输出

Debugger listening on ws://127.0.0.1:16772/202b2f5e-3671-4fce-aca9-d2cdf175bc69

For help see

https://nodejs.org/en/docs/inspector


表示已经成功的启动了监听。

b. 打开前端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Windows PowerShell
版权所有 (C) Microsoft Corporation。保留所有权利。

PS E:pomeloTestgame-server> cd ..
PS E:pomeloTest> cd .web-server
PS E:pomeloTestweb-server> node app
Warning: express.createServer() is deprecated, express
applications no longer inherit from http.Server,
please use:

  var express = require("express");
  var app = express();

connect.multipart() will be removed in connect 3.0
visit https://github.com/senchalabs/connect/wiki/Connect-3.0 for alternatives
connect.limit() will be removed in connect 3.0
Web server has started.
Please log on http://127.0.0.1:3001/index.html

c. 按F5启动调试;

在如图的位置添加断点(app_types/servers/handler/entryHandler.ts)


启动前端:

http://127.0.0.1:3001/index.html


点击“Test Game Server”

这时候可以看到VSCode中的断点已命中的效果:


恭喜你,说明你成功了。

注意事项

如果没有命中断点,请对着教程中的步骤检查下流程,看看哪里有不一样的地方,或者下方留言问题。

注意一下几个地方:

  1. tsconfig.json配置文件中的“outDir”和launch.json中的 “outFiles”中的路径请保持一致。
  2. tsconfig.json配置中需要添加“sourceMap”,这是为了在生成时添加.map文件,以至于调试的时候能通过js查找到对应的ts代码。
  3. launch.json配置中添加“sourceMaps”和“outFiles”将调试的js脚本定位到js的生成目录,以便于映射到对应的ts代码。
  4. launch.json配置中的端口请和server.json配置中的端口保持一致。

gitHub:

项目git:

https://github.com/Visow/pomelo-game-server-demo


PS: 获取完之后需要执行 “npm-install.bat”