游戏网络时钟
全库目录221
本文目录10
两端时钟的起点不同。客户端在本地第10秒收到“服务器第35秒”的快照,单看两个数字无法判断快照在网络上走了多久。网络时钟先估计两个时间轴的偏移,再把服务器时间换成模拟 Tick;测量的误差决定这份估计能用于多精细的判定。
四个时间戳
一次探测记录客户端发送时刻 、服务器接收时刻 、服务器发送时刻 、客户端接收时刻 。每一端在自己的单调时间轴上计时,单位都是秒。服务器处理耗时是 ,从整个往返时间里减掉它,才能估计链路往返延迟:
\theta=\frac{(t_1-t_0)+(t_2-t_3)}{2}$$ 在链路对称且短窗口内时钟速率相同的近似下,服务器时间约为客户端时间加 $\theta$。这两式对应 RFC 5905 第8节的时间戳关系;本页只使用其测量关系,不实现完整 NTP。 例如客户端在0秒发送、0.4秒收到,服务器在5.1秒收到、5.3秒回复。服务器处理花了0.2秒,链路往返也是0.2秒,偏移估计为5秒。只用一个服务器时间戳会把处理等待混入偏移。 ```python def clock_sample(t0, t1, t2, t3): offset = ((t1 - t0) + (t2 - t3)) / 2 network_rtt = (t3 - t0) - (t2 - t1) return offset, network_rtt ``` 这是 Python 数值片段,输入来自同一次握手。协议层先检查探测ID、顺序、有限数值及会话身份,再接纳样本。它不能由四个时间戳分辨真实偏移与固定链路不对称:上行150毫秒、下行50毫秒时,偏移会高估50毫秒。 ## 平滑偏移与模拟步长 连续样本会带队列抖动。可以在短窗口里优先使用低延迟样本,并限制每次偏移调整量;低延迟能减少排队影响,却不能消除固有路径不对称。长时间运行还会出现频率偏差,此时以 $S(l)=a l+b$ 表示服务器时间估计,缓慢调整斜率 $a$ 和偏移 $b$,避免一个噪声样本突然改变事件年龄。 服务器发送纪元ID、基准 Tick、对应服务器时间和固定步长。客户端用这份锚点计算当前估计 Tick,重连或重启改变纪元时清空旧样本。偏移修正用于显示时间和调度估计;物理仍消费约定的固定步长。调时直接改变一次物理步长,会同时改变碰撞与输入积分。 ## 偏移更新与Tick换算 一种明确的教学过滤器保存最近8个有效探测,取其中网络RTT最小的样本偏移作为目标。每次更新把当前偏移向目标移动,幅度限制为本地经过时间乘允许的最大校正速率;例如上次偏移5.000秒,目标5.020秒,经过0.1秒、校正速率0.01秒/秒,本次目标偏移只移到5.001秒。这是尚未向显示层发布时间的初始化阶段;开始发布后使用下面保持锚点连续的更新规则。锚点数量和跨度足够、频率拟合开始后,相位目标使用拟合映射,单样本偏移过滤随之结束,两份过滤器不会同时调整时间。长期漂移由下面的频率估计处理,参数按录制网络条件校准。 ### 频率拟合与连续更新 每次探测同时得到本地中点`l=(t0+t3)/2`和服务器中点`S=(t1+t2)/2`。每8个有效探测保留RTT最小的一对中点,累计最近16个锚点。只有锚点跨度超过5秒才拟合频率,避免把几毫秒排队误差除以很短时间放大成时钟漂移;尚不足时沿用上次斜率,初始为1。 对选定锚点求均值l̄、S̄,最小二乘斜率为`aFit=Σ((l-l̄)(S-S̄))/Σ((l-l̄)^2)`,截距为`bFit=S̄-aFit*l̄`。分母接近零便不更新。教学允许范围为`1±200×10^-6`,拟合超出时记录异常并截到该范围;持续超出则重建会话时钟,不能假定误差一定来自晶振。固定路径不对称仍进入截距偏差,统计拟合不会消除它。 保存映射时使用局部锚点形式`S(l)=Sref+a*(l-lref)`,减少大时间戳相减的精度损失。更新到本地时刻now时,先用旧映射算oldNow,再按拟合算targetNow。新锚点严格取`Sref=oldNow,lref=now`,更新瞬间保持同一个输出值;相位差不能直接从Sref减去,否则即使只减1毫秒,也会让刚发布的时间倒退。单独保存物理频率估计aClock,每秒最多移动20ppm并保持在上述允许范围;相位修正速率取`phaseRate=clamp((targetNow-oldNow)/1秒,−0.01,0.01)`,下一段映射斜率使用`a=aClock+phaseRate`。因此物理频偏与暂时追相位的速率分别记录,实际输出斜率最低约0.9898,始终为正。每次新锚点仍由上一映射在now的输出建立,误差通过后续流速消化,不在更新点跳变。连续三组低延迟锚点给出的相位差绝对值超过0.5秒时进入重新同步:暂停依赖估计时刻的显示与历史命中请求,递增本地同步代次并清空估计样本,重新建立锚点后恢复。服务器纪元由服务器决定,客户端重新同步不擅自改变服务器纪元;本地同步代次用于拒绝旧映射下已排队的结果。 例如真实关系为`S=1.0001*l+5`,相隔100秒的无排队锚点给出斜率1.0001,即100ppm。强行固定a=1,每100秒会累积0.01秒误差。拟合后偏移限制负责平缓移到这条时间线,斜率负责减少后续持续偏离。显示插值仍有自己的单调播放时间,见 [快照同步](/knowledge/21_%E6%B8%B8%E6%88%8F%E7%BD%91%E7%BB%9C/%E5%BF%AB%E7%85%A7%E5%90%8C%E6%AD%A5);物理输入消费仍由固定Tick计数驱动,不把估计时间的一次修正变成变长物理步。 ### 估计代次与播放重建 时钟重新同步会产生新的本地映射代次。所有使用估计时间的异步结果携带这份代次;新映射就绪后,旧代次的显示任务和待发历史命中请求直接失效。网络输入会话与服务器纪元保持原身份,已提交业务结果仍按原输入ID查询,不能因为本地调时就重发一次开火。 映射恢复后把新的目标时间交给快照播放状态机,由它在已有样本范围内重建r。新目标为10秒而最新可用快照只有9秒时,不能把画面直接宣布为10秒;仍等待合法左右样本或采用快照页规定的有限外推。时钟回答“现在约为多少”,缓冲回答“目前能显示哪个时刻”,固定模拟Tick则回答“已经执行了几步”,三个值分别记录。 ### Tick锚点 给定服务器锚点时间S0=10秒、Tick0=600、固定步长1/60秒,服务器时间估计10.05秒对应Tick603。代码用floor((S−S0)/dt)+Tick0,并保留分数供显示插值;浮点边界采用协议统一的整数时间单位或容差规则,避免两端在恰好一步处相差一Tick。新纪元的Tick600不能与旧纪元600比较,先换纪元并清空历史,才能接受新的时间轴。大幅错误锚点按上述0.5秒和连续样本规则重建本地同步,不通过一次超大物理步长追回。 ## 验证方法 分别注入对称延迟、非对称延迟、服务器处理等待和丢失探测。比较估计偏移、链路RTT和样本接纳原因。墙钟跳变不应改变单调计时;大幅校正采用重新建锚点的策略,并明确暂停、追帧或显示平滑的行为。 ## 实现与证据 2026-10-22,以本机 Python 3.11 运行 `python 知识库/examples/revision_16_21/test_reference.py`,17项检查通过。参考实现与测试输入可复现本页注明的机制。 本页对应两个四时间戳测试;连续锚点参考及test_clock.py检查负相位不倒退、正相位只改未来速率、最慢修正仍正向、频偏独立限速,2026-10-23执行4项通过。尚未接入真实时钟同步服务,网络时延与漂移采用数值输入。 ## 相关主题 - [快照同步](/knowledge/21_%E6%B8%B8%E6%88%8F%E7%BD%91%E7%BB%9C/%E5%BF%AB%E7%85%A7%E5%90%8C%E6%AD%A5) - [进程与调度](/knowledge/16_%E7%BC%96%E7%A8%8B%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E8%BF%9B%E7%A8%8B%E4%B8%8E%E8%B0%83%E5%BA%A6) - [游戏网络传输](/knowledge/21_%E6%B8%B8%E6%88%8F%E7%BD%91%E7%BB%9C/%E6%B8%B8%E6%88%8F%E7%BD%91%E7%BB%9C%E4%BC%A0%E8%BE%93) - [RPC](/knowledge/21_%E6%B8%B8%E6%88%8F%E7%BD%91%E7%BB%9C/RPC) ## 参考资料 - [RFC 5905](https://www.rfc-editor.org/rfc/rfc5905):第8节四时间戳公式、第10节时钟过滤;2026-10-22核读。