快速上手多人遊戲伺服器開發。後續會基於 Google Agones
,更新相關 K8S
運維、大規模快速擴充套件專用遊戲伺服器的文章。擁抱☁️原生? Cloud-Native!
系列
- ColyseusJS 輕量級多人遊戲伺服器開發框架 - 中文手冊(上)
- ColyseusJS 輕量級多人遊戲伺服器開發框架 - 中文手冊(中)
- ColyseusJS 輕量級多人遊戲伺服器開發框架 - 中文手冊(下)
監控皮膚 (@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
工具非常有用。
安裝
安裝 @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()
安裝
-
安裝
@colyseus/social
模組。
npm install @colyseus/social
npm install express-jwt
Import
並expose
由@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 連線 URIJWT_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"
});
//
// 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' });
更新使用者資料
您可以從客戶端修改 username
、displayName
、avatarUrl
、lang
、location
和 timezone
,然後呼叫 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
每當房間 spanwed
或 disposed
時都要記錄。
colyseus:matchmaking spawning 'chat' on worker 77218 +52s
colyseus:matchmaking disposing 'chat' on worker 77218 +2s
部署
Heroku
Heroku
僅用於原型設計。你可以通過點選這個按鈕來部署 colyseus-examples 專案:
Nginx (推薦)
建議在生產環境中使用 pm2
和 nginx
。
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
時,你不應該在它背後配置任何反向代理,比如 Nginx 或 Apache。
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.json
和package-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
擴充套件到多個程式或伺服器,你需要有 Redis
、MongoDB
和一個動態代理(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
用於儲存和查詢可用於 matchmaking
的 rooms
。
執行多個 Colyseus 程式
要在同一臺伺服器上執行多個 Colyseus
例項,需要每個例項監聽不同的埠號。建議使用 3001
、3002
、3003
等埠。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
公眾號:黑客下午茶
加我微信(互相學習交流),關注公眾號(獲取更多學習資料~)