当前位置: 首页 > news >正文

[Full Clock 技术复盘] 一、浏览器前端如何实现百毫秒级时间校准?时间 API 推荐、模拟 NTP 算法原理及局限

如 https://www.cnblogs.com/cup11/p/20207070 所述,接下来几天我将要发表关于全屏时钟的技术解析的文章。

前端时间校准的基本原理

电脑本地和服务器都可以使用 NTP 协议执行时间同步,但是 NTP 依赖于 UDP 而与 TCP 分离,在浏览器环境不可用,我们应该怎么办?

首先,无论是 NTP 还是前端的模拟校准算法,我们都需要理解一个最大的困境:

请求发送和接收的延迟

道理很简单,客户端发出一个请求,100 ms 后收到服务器答复:服务器在 00:00:30.000 时收到请求,在 00:00:30.006 时返回请求。客户端怎么知道自己要把时间设置到什么地方呢?

显然,没有任何软件手段能获知请求的去程和响应的回程在路上传输的时间。

所以,我们必须引入一个核心假设去程和返程的延迟是相同的

这很合理,因为一般而言往返走的线路不会有太大差异,但是也引入了 NTP 的最大误差,幸运的是,通过多次请求和寻找最佳服务器线路,精度可控制在亚毫秒级别

若你想了解生产生活中人们如何获取更高的精度的授时,可以搜索:PTPGNSS/GPS 授时技术

回到具体的例子,我们可以发现,“在路上”的请求时间是 100-6=94 ms,通过假设计算得单程延迟 94/2=47 ms,所以我们只需将本地时间设置为:00:00:30.006 + 0.047 = 00:00:30.053 即可。


前端的思路就是模仿这种策略,吸取其核心假设:

去程和返程的延迟是相同的。

那么,时间同步的服务去哪里找?自己后端搭一个当然可以,但是前提是保证服务器的时间准确。有没有现成的?我询问了各大 AI,给了我很多网站,有的需要认证,有的已经停止维护。我进行了逐家验证,最后向大家我推荐两个截至发文无需认证免费使用、较为稳定的后端时间请求 API

  1. https://api.shijian.online/timestamp/。它的返回格式如下:
    {"status":1,"data":{"timestamp":1780057227741}}
    
  2. https://timeapi.io/api/Time/current/zone?timeZone=UTC。它的返回格式如下:
    {"year":2026,"month":5,"day":30,"hour":13,"minute":17,"seconds":51,"milliSeconds":112,"dateTime":"2026-05-30T13:17:51.1120409","date":"05/30/2026","time":"13:17","timeZone":"UTC","dayOfWeek":"Saturday","dstActive":false
    }
    

都至少精确到毫秒。所以我们比较服务器接受到请求的那一刻的本地和云端时间:

  • 本地时间:(start_time + end_time) / 2(估计,最大误差为 (end_time - start_time) / 2);云端时间:API 返回的时间。
  • 如能访问成功两个同步服务器,我们选择 逆方差加权(如有想了解的读者可自行搜索)。简单来说,延迟越可信度(权重)呈平方级增长
  • 为防止超时,我们将超时设为 1.5s,因为长时间低精度的时间同步本就失去了意义。
  • 尤其注意时区处理和校准调时的前后方向,重点测试,不要闹笑话,一下子差掉几个小时,或者越调越偏。

代码实现

interface TimeSyncProvider {name: string;url: string;toTimestamp(json: any): number;
}interface SyncResult {drift: number; // 定义偏移量 = 服务器时间 - 本地时间rtt: number;   // 往返延迟 (Round-Trip Time)
}/*** 获取单个源的时间偏移*/
async function getOneDrift(provider: TimeSyncProvider): Promise<SyncResult | null> {const start = Date.now();try {const response = await fetch(provider.url, { cache: 'no-store',signal: AbortSignal.timeout(1500) // 1.5s 超时断开});const json = await response.json();const end = Date.now();const serverTime = provider.toTimestamp(json);const rtt = end - start;// 核心逻辑:假设服务器收到请求的时刻是 (start + end) / 2// drift = serverTime - (start + end) / 2const drift = serverTime - (start + rtt / 2);return { drift, rtt };} catch (e) {console.warn(`同步源 ${provider.name} 请求失败:`, e);return null;}
}/*** 逆方差加权融合多个源的结果*/
async function getWeightedDrift(providers: TimeSyncProvider[]): Promise<number | null> {const results = await Promise.all(providers.map(p => getOneDrift(p)));const validResults = results.filter((r): r is SyncResult => r !== null);if (validResults.length === 0) return null;// 逆方差加权逻辑:权重 w = 1 / (rtt^2)。延迟越低,可信度呈平方级增长。let totalWeight = 0;let weightedDrift = 0;validResults.forEach(res => {const weight = 1 / Math.pow(Math.max(res.rtt, 1), 2);weightedDrift += res.drift * weight;totalWeight += weight;});return weightedDrift / totalWeight;
}const providers: TimeSyncProvider[] = [{name: "shijian.online",url: "https://api.shijian.online/timestamp/",toTimestamp: (json) => json.data.timestamp},{name: "timeapi.io",url: "https://timeapi.io/api/Time/current/zone?timeZone=UTC",toTimestamp: (json) => new Date(json.dateTime).getTime()}
];getWeightedDrift(providers).then(finalDrift => {console.log(`最终计算得到的本地时间偏差: ${finalDrift.toFixed(2)}ms`);// 展示时间 = Date.now() + finalDrift
});

误差来源分析

读者有没有好奇,为什么我们一直在说这种方式是“粗略”的?难道说,NTP 和我们 HTTP 走的不是同一条线路?

并非如此。网络路径的不对称性本身即是造成 NTP 的最大误差来源,而在浏览器中,我们看到的是加剧误差的机制:

  1. TCP:为了保证安全可靠的传输,浏览器不支持裸 UDP,而要再进行三次握手,极大增加的请求处理的延迟。
  2. 单线程:浏览器 JavaScript 和渲染共用同一线程,渲染任务可能造成请求的延时处理。
  3. 读取时间的精准性:计算机对 NTP 发来的请求会更加优先地调度资源读取当前时间,而网页端需要经过浏览器调度,浏览器又要经过操作系统调度。

后记

这个功能前前后后打磨了三四个小时,希望经验分享能帮到有类似想法的读者。

若读者对此项目本身感兴趣,可阅读项目介绍:https://www.cnblogs.com/cup11/p/20207070

欢迎交流!

http://www.jsqmd.com/news/932226/

相关文章:

  • 2026马鞍山市防水补漏公司权威推荐:卫生间、阳台、屋顶、地下室、飘窗、外墙漏水,专业防水公司TOP5口碑榜+全维度测评(2026年6月最新深度行业资讯) - 防水百科
  • Bose SoundDock蓝牙改造:从30针接口到高保真无线音频的工程实践
  • Joy-Con Toolkit:Switch手柄终极定制与修复完整指南
  • 华为韬(T)定律
  • PlayCover终极指南:在Apple Silicon Mac上运行iOS应用的完整解决方案
  • 还在用HDMI转VGA?聊聊RK3568开发板上那颗RTD2166芯片的DP转VGA方案
  • 未来已来:五大新兴技术岗位解析与转型行动指南
  • GetQzonehistory:Python实现QQ空间历史说说完整备份指南
  • 2026丽水市防水补漏公司权威推荐:卫生间、阳台、屋顶、地下室、飘窗、外墙漏水,专业防水公司TOP5口碑榜+全维度测评(2026年6月最新深度行业资讯) - 防水百科
  • 2026长沙市防水补漏公司权威推荐:卫生间、阳台、屋顶、地下室、飘窗、外墙漏水,专业防水公司TOP5口碑榜+全维度测评(2026年6月最新深度行业资讯) - 防水百科
  • 零门槛3D打印智能火箭灯:免焊接编程的STEAM创客项目
  • 避坑指南:华为AR2220路由器配置这些细节错了,网络直接‘瘫痪’
  • 从Canny到OpenPose:一文搞懂ControlNet八大预处理器怎么选(附效果对比图)
  • STC8H单片机GPIO模式选错,你的LED为啥不亮?准双向、推挽、开漏、高阻输入模式保姆级选择指南
  • AI驱动企业增长:七种实战策略与实施路线图
  • 告别白屏卡顿!用MUI+HBuilderX快速搞定一个流畅的WebApp(附完整项目代码)
  • 别再用Stable Video Diffusion了:Sora 2虚拟偶像视频质量跃迁实测——PSNR↑41.7%,唇形同步误差↓至0.3帧(附基准测试数据集)
  • Self-Distillation不只是涨点:深入浅出聊聊它如何缓解梯度消失与网络‘懒惰’问题
  • 如何彻底告别Windows桌面混乱:免费开源神器NoFences终极指南
  • ncmdumpGUI:高效音频格式转换的专业解决方案
  • 从F1到F30:手把手教你用Matlab拆解CEC2017测试函数的‘脾气’(附避坑指南)
  • 变循环发动机建模与分布式控制策略【附仿真】“
  • 从胚胎发育到病理模拟,Sora 2生物动画生成如何实现亚毫秒级生物力学推演?2024 Q2全球仅17家机构获准调用全量API
  • 抽沙船哪里买 - 舒雯文化
  • 基于双目立体视觉的羊体尺参数提取及三维重构的解析方案【附代码】
  • 碧蓝航线自动化终极指南:3步实现游戏智能托管
  • 如何在Windows上5分钟搭建你的个人语音助手:完全离线、零隐私泄露的终极方案
  • 2026衡阳市防水补漏公司权威推荐:卫生间、阳台、屋顶、地下室、飘窗、外墙漏水,专业防水公司TOP5口碑榜+全维度测评(2026年6月最新深度行业资讯) - 防水百科
  • 多尺度建模革新:12自由度肌肉力驱动膝关节有限元模型解析
  • 从 Java 8 到 Java 17:IDEA 2023 里创建 Spring Boot 项目的正确姿势与版本选择指南