ColyseusJS 輕量級多人游戲服務器開發框架 - 中文手冊(系統保障篇)


快速上手多人游戲服務器開發。后續會基於 Google Agones,更新相關 K8S 運維、大規模快速擴展專用游戲服務器的文章。擁抱☁️原生🤗 Cloud-Native!

系列

監控面板 (@colyseus/monitor)

@colyseus/monitor 是一個方便的工具,允許您查看和檢查服務器生成的當前房間列表。

特性

  • 列出所有活動房間
    • 強制安排一個特定的房間
  • 檢查一個特定的房間
    • 查看房間的狀態
    • 為客戶端發送/廣播消息
    • 強制斷開客戶端連接

安裝

安裝模塊:

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 工具非常有用。

asciicast

安裝

安裝 @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 項目:

Deploy

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
公眾號:黑客下午茶
加我微信(互相學習交流),關注公眾號(獲取更多學習資料~)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM