ColyseusJS 轻量级多人游戏服务器开发框架 - 中文手册(系统保障篇)

快速上手多人游戏服务器开发。后续会基于 Google Agones,更新相关 K8S 运维、大规模快速扩展专用游戏服务器的文章。拥抱️原生 Cloud-Native!

系列

监控面板 (@colyseus/monitor)

@colyseus/monitor 是一个方便的工具,允许您查看和检查服务器生成的当前房间列表。

特性

  • 列出所有活动房间
    • 强制安排一个特定的房间
  • 检查一个特定的房间
    • 查看房间的状态
    • 为客户端发送/广播消息
    • 强制断开客户端连接

ColyseusJS 轻量级多人游戏服务器开发框架 - 中文手册(系统保障篇)

安装

安装模块:

npm install --save @colyseus/monitor

将它包含在你的项目中:

// ...
import { monitor } from "@colyseus/monitor"; // ...
app.use("/colyseus", monitor());

使用密码限制访问面板

您可以使用 express 中间件在 monitor 路由上启用身份验证,例如 express-basic-middleware

npm install --save express-basic-auth

使用 express-basic-auth 创建用户和密码。

import basicAuth from "express-basic-auth";

const basicAuthMiddleware = basicAuth({
// list of users and passwords
users: {
"admin": "admin",
},
// sends WWW-Authenticate header, which will prompt the user to fill
// credentials in
challenge: true
}); app.use("/colyseus", basicAuthMiddleware, monitor());

设置自定义房间列表列

app.use("/colyseus", basicAuthMiddleware, monitor({
columns: [
'roomId',
'name',
'clients',
{ metadata: "spectators" }, // display 'spectators' from metadata
'locked',
'elapsedTime'
]
}));

如果未指定,则默认的房间列表列为:['roomId', 'name', 'clients', 'maxClients', 'locked', 'elapsedTime']

负载测试 / 压力测试 (@colyseus/loadtest)

当您想对服务器进行实战测试并了解它在实时环境中的性能时,@colyseus/loadtest 工具非常有用。

ColyseusJS 轻量级多人游戏服务器开发框架 - 中文手册(系统保障篇)

安装

安装 @colyseus/loadtest 模块:

npm install --save-dev @colyseus/loadtest

用法

colyseus-loadtest 命令需要一些参数才能工作:

  • script: 该工具将要使用的自定义脚本
  • --endpoint: 你服务器端点 (默认使用 ws://localhost:2567)
  • --room: 您要连接的房间名称
  • --numClients: 您想连接到 room 的客户端数量。

示例

这是一个脚本文件示例。基于每个连接客户端的房间生命周期事件,您可以实现一个 "bot" 来与 room 交互。

// script.ts
import { Room, Client } from "colyseus.js"; export function requestJoinOptions (this: Client, i: number) {
return { requestNumber: i };
} export function onJoin(this: Room) {
console.log(this.sessionId, "joined."); this.onMessage("*", (type, message) => {
console.log("onMessage:", type, message);
});
} export function onLeave(this: Room) {
console.log(this.sessionId, "left.");
} export function onError(this: Room, err) {
console.error(this.sessionId, "!! ERROR !!", err.message);
} export function onStateChange(this: Room, state) {
}

把 50 个客户端连接到一个 "battle" 房间

npx colyseus-loadtest script.ts --room battle --numClients 50 --endpoint ws://localhost:2567

认证 + 社交 (@colyseus/social)

本节介绍 @colyseus/social 的配置和用法。

@colyseus/social 是一个实验性模块,提供通用后端服务,以加快您的多人游戏开发体验。该 API 公开征求建议和改进。

如果要实现自己的身份验证方法,请参见 Room » onAuth()

安装

  1. 下载和安装 MongoDB

  2. 安装 @colyseus/social 模块。

npm install @colyseus/social
npm install express-jwt
  1. Importexpose@colyseus/social 提供的 Express 路由。
import express from "express";
import socialRoutes from "@colyseus/social/express" const app = express();
app.use("/", socialRoutes); app.listen(8080);
const express = require("express");
const socialRoutes = require("@colyseus/social/express").default; const app = express();
app.use("/", socialRoutes); app.listen(8080);

服务器端配置

环境变量

  • MONGO_URI: MongoDB 连接 URI
  • JWT_SECRET: 用于身份验证的安全 secret 字符串。
  • FACEBOOK_APP_TOKEN: Facebook App Token ("appid|appsecret")

服务器端 API

@colyseus/social 模块提供了 MongoDB models 和 token 验证功能供您使用。

import { User, FriendRequest, verifyToken } from "@colyseus/social";

实现 onAuth 去检索当前用户

import { User, verifyToken } from "@colyseus/social";

class MyRoom extends Room {

  async onAuth(client, options) {
// verify token authenticity
const token = verifyToken(options.token); // query the user by its id
return await User.findById(token._id);
} onJoin(client, options, user) {
console.log(user.username, "has joined the room!");
} }

Hooks

hooks.beforeAuthenticate

beforeAuthenticate 钩子在用户登录或注册之前被触发。

import { hooks } from "@colyseus/social";

hooks.beforeAuthenticate((provider, $setOnInsert, $set) => {
// assign default metadata upon registration
$setOnInsert.metadata = {
coins: 100,
trophies: 0
};
});

hooks.beforeUserUpdate

beforeUserUpdate 钩子在用户通过 save() 方法更新自己的信息之前被触发。

import Filter from "bad-words";
const filter = new Filter(); hooks.beforeUserUpdate((_id, fields) => {
if (fields['username'] && filter.isProfane(fields['username'])) {
throw new Error("no_swearing_allowed");
}
})

客户端 API

登录

匿名

await client.auth.login();

Email + 密码

await client.auth.login({
email: "user@example.com",
password: "12345"
});

Facebook

//
// Make sure you have the Facebook SDK installed and configured first
// - https://developers.facebook.com/docs/javascript/quickstart
// - https://developers.facebook.com/docs/facebook-login/web
// FB.login(function(response) {
if (response.authResponse) {
client.auth.login({ accessToken: response.authResponse.accessToken });
}
}, { scope: 'public_profile,email,user_friends' });

更新用户数据

您可以从客户端修改 usernamedisplayNameavatarUrllanglocationtimezone,然后调用 save() 方法。

client.auth.username = "Hello world!"
await client.auth.save();

注销

client.auth.logout();

获取朋友列表

const friends = await client.auth.getFriends();
friends.forEach(friend => {
console.log(friend.username);
});

获取在线朋友列表

const friends = await client.auth.getOnlineFriends();
friends.forEach(friend => {
console.log(friend.username);
});

获取朋友请求的列表

const friends = await client.auth.getFriendRequests();
friends.forEach(friend => {
console.log(friend.username);
});

接受朋友的请求

await client.auth.acceptFriendRequest(friendId);

拒绝朋友请求

await client.auth.declineFriendRequest(friendId);

发送朋友请求

await client.auth.sendFriendRequest(friendId);

阻止用户

await client.auth.blockUser(friendId);

取消阻止用户

await client.auth.unblockUser(friendId);

调试

Inspector

可以使用 Node.js 中的内置 inspector 来调试应用程序。

阅读更多关于 调试 Node.js 应用程序.

在生产环境中使用 inspector

在生产中使用 inspector 时要小心。使用内存快照和断点将直接影响用户的体验。

1. 连接到远程服务器:

ssh root@remote.example.com

2. 检查 Node 进程的 PID

ps aux | grep node

3. 将 inspector 附加到进程上

kill -usr1 PID

4. 创建一个从本地机器到远程 inspector 的 SSH tunnel

ssh -L 9229:localhost:9229 root@remote.example.com

您的生产服务器现在应该出现在 chrome://inspect 上。

Debug 消息

服务器提供了一些调试消息,您可以通过设置 DEBUG 环境变量来逐个启用这些消息。

要启用所有日志,可以使用以下命令运行服务器:

DEBUG=colyseus:* node server.js

请参阅下面所有可用的调试类别和示例输出。

colyseus:patch

记录向所有客户端广播补丁的字节数和时间间隔。

colyseus:patch "chat" (roomId: "ryWiL5rLTZ") is sending 28 bytes: +57ms

colyseus:errors

在服务器端发生意外(或内部预期)错误时记录日志。

colyseus:matchmaking

每当房间 spanweddisposed 时都要记录。

colyseus:matchmaking spawning 'chat' on worker 77218 +52s
colyseus:matchmaking disposing 'chat' on worker 77218 +2s

部署

Heroku

Heroku 仅用于原型设计。你可以通过点击这个按钮来部署 colyseus-examples 项目:

ColyseusJS 轻量级多人游戏服务器开发框架 - 中文手册(系统保障篇)

Nginx (推荐)

建议在生产环境中使用 pm2nginx

PM2

在您的环境中安装 pm2

npm install -g pm2

然后使用它启动你的服务器:

pm2 start your-server.js

Nginx 配置

server {
listen 80;
server_name yourdomain.com; location / {
proxy_pass http://localhost:2567;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}

使用 SSL 配置 Nginx

建议从 LetsEncrypt 获取证书。

server {
listen 80;
listen 443 ssl;
server_name yourdomain.com; ssl_certificate /path/to/your/cert.crt;
ssl_certificate_key /path/to/your/cert.key; location / {
proxy_pass http://localhost:2567;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}

Apache

下面介绍如何使用 Apache 作为 Node.js Colyseus 应用程序的代理(Thanks tomkleine!)

安装所需的 Apache 模块:

sudo a2enmod ssl
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_html
sudo a2enmod proxy_wstunnel

虚拟主机配置:

<VirtualHost *:80>
ServerName servername.xyz # Redirect all requests received from port 80 to the HTTPS variant (force ssl)
RewriteEngine On
RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L] </VirtualHost> <VirtualHost *:443>
ServerName servername.xyz # enable SSL
SSLEngine On
SSLCertificateFile /PATH/TO/CERT/FILE
SSLCertificateKeyFile /PATH/TO/PRIVATE/KEY/FILE #
# setup the proxy to forward websocket requests properly to a normal websocket
# and vice versa, so there's no need to change the colyseus library or the
# server for that matter)
#
# (note: this proxy automatically converts the secure websocket (wss) RewriteEngine On
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC,OR]
RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC]
RewriteRule .* ws://127.0.0.1:APP-PORT-HERE%{REQUEST_URI} [P,QSA,L] # setup the proxy to forward all https requests to http backend
# (also automatic conversion from https to http and vice versa) ProxyPass "/" "http://localhost:APP-PORT-HERE/"
ProxyPassReverse "/" "http://localhost:APP-PORT-HERE/" </VirtualHost>

greenlock-express

如果您希望在服务器上快速配置 SSL,而不需要配置反向代理(reverse-proxy),Greenlock 是一个很好的工具。

当使用 greenlock-express 时,你不应该在它背后配置任何反向代理,比如 NginxApache

npm install --save greenlock-express

请先遵循 greenlock-express 的 README 部分

下面是处理开发环境(development)和生产环境(production)的推荐方法:

import http from "http";
import express from "express";
import { Server } from "colyseus"; function setup(app: express.Application, server: http.Server) {
const gameServer = new Server({ server }); // TODO: configure `app` and `gameServer` accourding to your needs.
// gameServer.define("room", YourRoom); return app;
} if (process.env.NODE_ENV === "production") {
require('greenlock-express')
.init(function () {
return {
greenlock: require('./greenlock'),
cluster: false
};
})
.ready(function (glx) {
const app = express(); // Serves on 80 and 443
// Get's SSL certificates magically!
glx.serveApp(setup(app, glx.httpsServer(undefined, app)));
}); } else {
// development port
const PORT = process.env.PORT || 2567; const app = express();
const server = http.createServer(app); setup(app, server);
server.listen(PORT, () => console.log(`Listening on http://localhost:${PORT}`));
}

Docker

先决条件:

  • package.jsonpackage-lock.json 在项目中。

  • 设置 npm start 命令,使其启动服务器。

步骤:

Step 1 安装 Docker

Step 2 在 colyseus 项目的根目录中创建 Dockerfile

FROM node:12

ENV PORT 8080

WORKDIR /usr/src/app

# A wildcard is used to ensure both package.json AND package-lock.json are copied
COPY package*.json ./ RUN npm ci
# run this for production
# npm ci --only=production COPY . . EXPOSE 8080 CMD [ "npm", "start" ]

Step 3 在同一目录中创建 .dockerginore 文件

node_modules
npm-debug.log

这将防止您的本地模块和调试日志被复制到您的 Docker 镜像中,并可能覆盖安装在镜像中的模块。

Step 4 进入 Dockerfile 所在的目录,运行以下命令构建 Docker 镜像。-t flag 可以让你标记你的 image,这样以后使用 docker images 命令更容易找到:

docker build -t <your username>/colyseus-server .

Step 5 你的 image 现在将由 Docker 用以下命令列出:

docker images

输出:

# Example
REPOSITORY TAG ID CREATED
node 12 1934b0b038d1 About a minute ago
<your username>/colseus-server latest d64d3505b0d2 About a minute ago

Step 6 使用以下命令运行 Docker 镜像:

docker run -p 8080:8080 -d <your username>/colyseus-server

使用 -d 运行镜像将以 detached 模式运行容器,使容器在后台运行。-p flag 将公共端口重定向到容器内的私有端口。

Step 7 完成后,现在可以使用 localhost:8080 连接到服务器

更多信息:

高可用,可扩展

这个文档是一个正在进行的工作。

要将 Colyseus 扩展到多个进程或服务器,你需要有 RedisMongoDB 和一个动态代理(dynamic proxy)。

Redis

下载并安装 Redis。使用 RedisPresence

import { Server, RedisPresence } from "colyseus";

const gameServer = new Server({
// ...
presence: new RedisPresence(),
});
const colyseus = require("colyseus");

const gameServer = new colyseus.Server({
// ...
presence: new colyseus.RedisPresence(),
});

presence 用于从一个进程到另一个进程调用房间 "seat reservation" 功能,并允许开发人员跨 rooms 利用一些数据共享功能。请参阅 Presence API

每个 Colyseus 进程还将自己的 processId 和网络位置注册到 presence API,稍后 dynamic proxy 服务将使用该 API。在 graceful shutdown 期间,进程注销自己。

MongoDB

下载并安装 MongoDB。安装 mongoose 软件包:

npm install --save mongoose

使用 MongooseDriver:

import { Server, RedisPresence } from "colyseus";
import { MongooseDriver } from "colyseus/lib/matchmaker/drivers/MongooseDriver" const gameServer = new Server({
// ...
driver: new MongooseDriver(),
});
const colyseus = require("colyseus");
const MongooseDriver = require("colyseus/lib/matchmaker/drivers/MongooseDriver").MongooseDriver; const gameServer = new colyseus.Server({
// ...
driver: new MongooseDriver(),
});

您可以将 MongoDB 连接 URI 传递给 new MongooseDriver(uri) 构造函数,或者设置 MONGO_URI 环境变量。

driver 用于存储和查询可用于 matchmakingrooms

运行多个 Colyseus 进程

要在同一台服务器上运行多个 Colyseus 实例,需要每个实例监听不同的端口号。建议使用 300130023003 等端口。Colyseus 进程不应公开。只有 dynamic proxy 是。

强烈推荐使用PM2进程管理器来管理多个 Node.js 应用程序实例。

PM2 提供了一个 NODE_APP_INSTANCE 环境变量,其中包含每个进程的不同编号。使用它来定义端口号。

import { Server } from "colyseus";

// binds each instance of the server on a different port.
const PORT = Number(process.env.PORT) + Number(process.env.NODE_APP_INSTANCE); const gameServer = new Server({ /* ... */ }) gameServer.listen(PORT);
console.log("Listening on", PORT);
npm install -g pm2

使用以下 ecosystem.config.js 配置:

// ecosystem.config.js
const os = require('os');
module.exports = {
apps: [{
port : 3000,
name : "colyseus",
script : "lib/index.js", // your entrypoint file
watch : true, // optional
instances : os.cpus().length,
exec_mode : 'fork', // IMPORTANT: do not use cluster mode.
env: {
DEBUG: "colyseus:errors",
NODE_ENV: "production",
}
}]
}

现在你已经准备好开始多个 Colyseus 进程了。

pm2 start

"PM2 和 TypeScript":建议在运行 pm2 start 之前通过 npx tsc 编译 .ts 文件。或者,你可以为 PM2 安装TypeScript 解释器(pm2 install typescript),并设置 exec_interpreter: "ts-node"(read more)。

动态代理

@colyseus/proxy 是一个动态代理,它会自动监听 Colyseus 进程的上下变化,允许 WebSocket 连接到创建了房间的正确进程和服务器上。

代理应该绑定到端口 80/443,因为它是应用程序惟一的公共端点。所有请求都必须通过代理。

npm install -g @colyseus/proxy

环境变量

配置以下环境变量以满足您的需求:

  • PORT 是代理将运行的端口。
  • REDIS_URL 是你在 Colyseus 进程中使用的同一个 Redis 实例的路径。

运行代理

colyseus-proxy

> {"name":"redbird","hostname":"Endels-MacBook-Air.local","pid":33390,"level":30,"msg":"Started a Redbird reverse proxy server on port 80","time":"2019-08-20T15:26:19.605Z","v":0}

Refs

中文手册同步更新在:

  • https:/colyseus.hacker-linner.com
我是为少
微信:uuhells123
公众号:黑客下午茶
加我微信(互相学习交流),关注公众号(获取更多学习资料~)
上一篇:ColyseusJS 轻量级多人游戏服务器开发框架 - 中文手册(中)


下一篇:phpredis 中文手册和redis 教程