快速上手多人游戲服務器開發。后續會基於 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
公眾號:黑客下午茶
加我微信(互相學習交流),關注公眾號(獲取更多學習資料~)