我正在用node.js和socket.io构建一个回合制的文本游戏。每个回合都有一个超时,之后玩家失去转弯并且传递给下一个玩家。我正在another question中使用setTimeout
函数。
问题在于我不知道如何在多个实例和多个服务器上进行扩展。 AIUI,如果我设置超时,我只能在同一个实例中清除它。因此,如果玩家失去转弯,例如,超时将与其他玩家转弯续订,但是这个新玩家将无法访问计时器对象以清除它,因为它正在第一个玩家的实例上运行。
我查看了Redis的pub / sub功能(无论如何我都要使用它),但我没有找到任何关于定时事件或延迟发布的信息。
TL; DR,如何保留与实例/服务器无关的计时器?
答案 0 :(得分:0)
我发现的解决方案是使用一些消息系统(在我的情况下是Redis pub / sub)来保持每个玩家实例了解当前状态。
每个玩家都有一个工人实例,可以自己处理(包括计时器)。当它完成时,无论是通过玩家的移动还是通过超时,它都会使转弯计数器前进,并通过pub / sub通过新的转弯数通知所有实例。所有实例都会收到该消息并将转弯数与其自己的玩家号码进行比较。如果匹配,则实例处理转弯并重复循环。
我将尝试提供一个示例(更多的伪代码):
// pub & sub are Redis publisher & subscriber clients, respectively
function Game (totalPlayers, playerNumber) {
this.turn = 0
this.totalPlayers = totalPlayers
this.playerNumber = playerNumber
// Subscribe to Redis events
sub.on('message', function (channel, message) {
message = JSON.parse(message)
switch(message.type) {
case 'turn':
this.onTurn(message.turn)
}
})
sub.subscribe(this.channel, function() {
this.checkStart()
})
}
Game.prototype.checkStart = function () {
// This checks if this instance is for
// the last player and, if so, starts the
// main loop:
if(this.playerNumber == this.totalPlayers - 1) {
pub.publish(this.channel, JSON.stringify({type: 'turn', turn: 0})
}
}
Game.prototype.onTurn = function(turn) {
this.turn = turn
if(this.turn == this.playerNumber) {
this.timer = setTimeout(this.endTurn.bind(this), this.turnTime)
}
}
Game.prototype.endTurn = function() {
this.turn = (this.turn + 1) % this.totalPlayers
pub.publish(this.channel, JSON.stringify({type: 'turn', turn: this.turn})
}
我在使用这种方法时遇到了一些问题,主要问题是初始状态,如果玩家几乎同时连接,那么这种状态并不完全正确。发送信息并确保所有实例都处于同步状态也是一个好主意。
如果有人遇到同样的问题,我希望我明白这一点。
答案 1 :(得分:0)
使用Redis及其TTL选项(加上发布/订阅机制)可以完成可靠的独立计时器。
//enable keyspace events:
redisClient.send_command('config', ['set', 'notify-keyspace-events', 'Ex']);
// add a key:
const key = '<some meaningful key string>';
redisClient.set(key, '<some note for the key, not usable though>');
// set the key to expire:
redisClient.expire(key, 100); // duration in seconds
// somewhere else in the code, subscribe to the 'expired' event:
const expiredSubKey = `__keyevent@${config.redis.db}__:expired`; // you need redis DB number here
redisClient.subscribe(expiredSubKey, () => {
redisClient.on('message', async (channel, key) => {
// channel is actually expiredSubKey, ignore it
// 'key' is the key you've set up previously
});
});
(更多信息:How to receive Redis expire events with node?)
除了不依赖服务之外,该技术还有一个优势:
它也有一些缺点:
您也可以为此使用一些第三方服务。我能够找到其中一些免费计划和合理的API(尽管我使用的是我自己的,如上文所述)。