在我的 Meteor 应用程序中实现回合制多人游戏服务器,客户端通过发布/订阅接收游戏状态,并且可以调用 Meteor 方法sendTurn
将回合数据发送到服务器(他们无法直接更新游戏状态集合)。
var endRound = function(gameRound) {
// check if gameRound has already ended /
// if round results have already been determined
// --> yes:
do nothing
// --> no:
// determine round results
// update collection
// create next gameRound
};
Meteor.methods({
sendTurn: function(turnParams) {
// find gameRound data
// validate turnParams against gameRound
// store turn (update "gameRound" collection object)
// have all clients sent in turns for this round?
// yes --> call "endRound"
// no --> wait for other clients to send turns
}
});
为了实现时间限制,我想等待一定的时间段(让客户有时间打电话sendTurn
),然后确定回合结果 - 但前提是回合结果尚未确定sendTurn
.
我应该如何在服务器上实现这个时间限制?
我天真的实现方法是调用Meteor.setTimeout(endRound, <roundTimeLimit>)
.
问题:
那么并发呢?我假设我应该同步更新集合(没有回调)sendTurn
and endRound
(?),但这足以消除竞争条件吗? (阅读已接受答案的第四条评论这个问题 https://stackoverflow.com/questions/18007014/meteor-could-a-race-condition-happen-with-meteor-collections-on-server-side关于同步数据库操作也会产生,我对此表示怀疑)
-
在这方面,“根据请求”在流星文档 http://docs.meteor.com/#/full/structuringyourapp在我的上下文中(函数endRound
由客户端方法调用和/或在服务器中调用setTimeout
)?
在 Meteor 中,您的服务器代码在每个请求的单个线程中运行,而不是以 Node 典型的异步回调风格运行。
在多服务器/集群环境中,(如何)这将工作?
很好的问题,而且它比看起来更棘手。首先,我想指出,我已经在以下存储库中实现了针对这个确切问题的解决方案:
https://github.com/ldworkin/meteor-prisoners-dilemma https://github.com/ldworkin/meteor-prisoners-dilemma
https://github.com/HarvardEconCS/turkserver-meteor https://github.com/HarvardEconCS/turkserver-meteor
总结一下,问题基本上有以下几个特点:
- 每个客户端在每一轮中发送一些操作(您将其称为
sendTurn
)
- 当所有客户端都发送了他们的操作后,运行
endRound
- 每轮都有一个计时器,如果到期,则自动运行
endRound
anyway
-
endRound
无论客户端做什么,每轮都必须执行一次
现在,考虑我们必须处理的 Meteor 的属性:
- 每个客户端一次只能有一个连接到服务器的未完成的方法(除非
this.unblock()
在方法内部调用)。以下方法等待第一个方法。
- 服务器上的所有超时和数据库操作都可以让步给其他纤程
这意味着每当方法调用经历屈服操作时,Node 或数据库中的值都可能发生变化。这可能会导致以下潜在的竞争条件(这些只是我已修复的条件,但可能还有其他条件):
- 例如,在 2 人游戏中,两个客户端调用
sendTurn
完全在同一时间。两者都调用屈服操作来存储转弯数据。两种方法都会检查是否有 2 个玩家轮流发送,找到肯定的结果,然后endRound
运行两次。
- 一名玩家打电话
sendTurn
就在回合结束时。在这种情况下,endRound
被超时和玩家的方法调用,导致再次运行两次。
- 对上述问题的错误修复可能会导致饥饿
endRound
永远不会被叫到。
您可以通过多种方式解决此问题,可以在 Node 中同步,也可以在数据库中同步。
- 由于一次只有一个 Fiber 实际上可以更改 Node 中的值,因此如果您不调用屈服操作,则可以保证避免可能的竞争条件。因此,您可以将回合状态等内容缓存在内存中,而不是数据库中。然而,这要求缓存正确完成并且不会延续到集群环境。
- 移动
endRound
方法调用本身之外的代码,使用其他东西来触发它。这是我采取的方法,确保只有计时器或最终玩家触发回合结束,而不是两者都触发(请参阅here https://github.com/ldworkin/meteor-prisoners-dilemma/blob/master/server/server.js#L121对于使用的实现observeChanges
).
-
在集群环境中,您必须仅使用数据库进行同步,可能需要使用条件更新操作和原子运算符。像下面这样:
var currentVal;
while(true) {
currentVal = Foo.findOne(id).val; // yields
if( Foo.update({_id: id, val: currentVal}, {$inc: {val: 1}}) > 0 ) {
// Operation went as expected
// (your code here, e.g. endRound)
break;
}
else {
// Race condition detected, try again
}
}
上述方法比较原始,在高负载下可能会导致数据库性能不佳;它也不处理计时器,但我相信通过一些思考,您可以弄清楚如何扩展它以更好地工作。
您可能还想看这个定时器代码 https://github.com/HarvardEconCS/turkserver-meteor/blob/master/lib/timers_server.js一些其他的想法。一旦我有时间,我将把它扩展到你所描述的完整设置。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)