以可靠的方式找出延迟
Find out latency in a reliable way
背景:我正在开发一款小游戏,利用玩家的延迟来做lag compensation。该游戏是开源的,因此目前很容易对系统进行逆向工程并延迟响应时间以人为增加报告的延迟,从而可能导致不公平的优势。
我目前的延迟检索策略是:
- 每隔固定的时间间隔,我会向玩家发送一条标记为 "ping" 的消息。 (这与ICMP无关)
- 此 ping 消息由一个特殊的 "ping" 操作码和一个带有序列号的有效载荷组成
- 一旦客户端收到上述消息,他就会发回一个带有 "pong" 操作码和具有相同序列号的有效负载
- 当服务器收到标记为 "pong" 的消息时,它会计算发送和接收之间经过的时间。这是往返时间
- 我们的延迟是 rtt / 2
伪代码
服务器:
function now() {
return current UTC time in millis
}
i = 0
function nextSequence() {
return i++
}
sendingTimestamps = []
function onPingEvent() {
id = nextSequence()
sendingTimestamps[id] = now()
sendPingMessage(id)
}
function onPongReceived(id) {
received = now()
sent = sendingTimestamps[id]
rtt = received - sent
latency = rtt / 2
}
客户:
function onPingReceived(id) {
sendPongMessage(id)
}
如您所见,客户很容易在他的代码中添加一个延迟来增加他报告的延迟。
是否有更好的方法来降低客户端延迟,从而减少作弊的空间?
下面的答案是评论中讨论的主题的摘要,以便将它们全部集中在一个地方。
滞后补偿应该依赖于事件的精确时间戳而不是平均数据包延迟
即使是两个连续的数据包,转换时间也可能有很大差异。建议的方法是测量平均延迟并假设每个接收到的数据包都是在 "latency" 毫秒前发送的以进行延迟补偿,这种方法太不准确了。应改为应用以下方案:
服务器开始在其端模拟世界并向所有客户端发送命令 START。客户端启动模拟世界并从其创建开始计算滴答声。每当客户端发生任何事件时,客户端都会将其与时间戳一起发送到服务器。喜欢"user pressed fire at tick #183"。由于数据包传输时间,服务器对游戏的模拟遥遥领先,但服务器可以"go back in time"处理用户的订单并解决后果。
时间戳和事件仍然可以伪造
AFAIU验证客户端输入的问题一般无法解决。客户端中实现的任何算法都可以重新创建以伪造 events/timestamps/packets。封闭的代码可以反转,所以不是答案。即使是像《反恐精英》或《守望先锋》这样遍布全球的游戏也有作弊器,尽管它们是由大公司开发的,我敢打赌,这些公司有单独的部门专门负责游戏安全。一些公司开发类似杀毒模块,检查游戏文件完整性或部分内存快照的哈希值,但它仍然可以被绕过。
问题是伪造算法所需的工作量。需要付出的努力越多,造假者就越少。简单的时间戳验证如下:
如果您在事件#1 之后在 TCP 流中接收到事件#2,但它的时间戳在事件#1 之前,那么它就是伪造的。
如果时间戳远远落后于服务器时间,则警告并踢玩家严重延迟。如果是真人玩家,反正他玩不了这个游戏,不然你就把黑客给踢了。如果我没记错的话,CS 服务器会这样做。
背景:我正在开发一款小游戏,利用玩家的延迟来做lag compensation。该游戏是开源的,因此目前很容易对系统进行逆向工程并延迟响应时间以人为增加报告的延迟,从而可能导致不公平的优势。
我目前的延迟检索策略是:
- 每隔固定的时间间隔,我会向玩家发送一条标记为 "ping" 的消息。 (这与ICMP无关)
- 此 ping 消息由一个特殊的 "ping" 操作码和一个带有序列号的有效载荷组成
- 一旦客户端收到上述消息,他就会发回一个带有 "pong" 操作码和具有相同序列号的有效负载
- 当服务器收到标记为 "pong" 的消息时,它会计算发送和接收之间经过的时间。这是往返时间
- 我们的延迟是 rtt / 2
伪代码
服务器:
function now() {
return current UTC time in millis
}
i = 0
function nextSequence() {
return i++
}
sendingTimestamps = []
function onPingEvent() {
id = nextSequence()
sendingTimestamps[id] = now()
sendPingMessage(id)
}
function onPongReceived(id) {
received = now()
sent = sendingTimestamps[id]
rtt = received - sent
latency = rtt / 2
}
客户:
function onPingReceived(id) {
sendPongMessage(id)
}
如您所见,客户很容易在他的代码中添加一个延迟来增加他报告的延迟。
是否有更好的方法来降低客户端延迟,从而减少作弊的空间?
下面的答案是评论中讨论的主题的摘要,以便将它们全部集中在一个地方。
滞后补偿应该依赖于事件的精确时间戳而不是平均数据包延迟
即使是两个连续的数据包,转换时间也可能有很大差异。建议的方法是测量平均延迟并假设每个接收到的数据包都是在 "latency" 毫秒前发送的以进行延迟补偿,这种方法太不准确了。应改为应用以下方案:
服务器开始在其端模拟世界并向所有客户端发送命令 START。客户端启动模拟世界并从其创建开始计算滴答声。每当客户端发生任何事件时,客户端都会将其与时间戳一起发送到服务器。喜欢"user pressed fire at tick #183"。由于数据包传输时间,服务器对游戏的模拟遥遥领先,但服务器可以"go back in time"处理用户的订单并解决后果。
时间戳和事件仍然可以伪造
AFAIU验证客户端输入的问题一般无法解决。客户端中实现的任何算法都可以重新创建以伪造 events/timestamps/packets。封闭的代码可以反转,所以不是答案。即使是像《反恐精英》或《守望先锋》这样遍布全球的游戏也有作弊器,尽管它们是由大公司开发的,我敢打赌,这些公司有单独的部门专门负责游戏安全。一些公司开发类似杀毒模块,检查游戏文件完整性或部分内存快照的哈希值,但它仍然可以被绕过。
问题是伪造算法所需的工作量。需要付出的努力越多,造假者就越少。简单的时间戳验证如下:
如果您在事件#1 之后在 TCP 流中接收到事件#2,但它的时间戳在事件#1 之前,那么它就是伪造的。
如果时间戳远远落后于服务器时间,则警告并踢玩家严重延迟。如果是真人玩家,反正他玩不了这个游戏,不然你就把黑客给踢了。如果我没记错的话,CS 服务器会这样做。