在多个设备之间同步JS时间

IT技术 javascript time synchronization
2021-02-21 06:30:36

我正在使用精彩的reveal.js库来创建HTML 幻灯片。我唯一的问题是我需要它在多个设备之间同步。

目前,我正在向来自服务器的时间发出 AJAX 请求,并为页面保留一个内部时钟。

function syncTime() {
    // Set up our time object, synced by the HTTP DATE header
    // Fetch the page over JS to get just the headers
    console.log("syncing time")
    var r = new XMLHttpRequest();
    r.open('HEAD', document.location, false);
    r.send(null);
    var timestring = r.getResponseHeader("DATE");

    systemtime = new Date(timestring); // Set the time to the date sent from the server
}

虽然这让我在 1 秒左右的准确度内得到了回报,但我需要做得更好。当幻灯片自动前进时,差异非常明显。

代码将全部运行在同一平台上,因此无需担心跨浏览器兼容性。

这是我设法整理的内容

有任何想法吗?

5个回答

测量发送请求和返回响应之间经过的时间。然后,将该值除以 2。这将为您提供单向延迟的粗略值。如果您将其添加到来自服务器的时间值,您将更接近真实的服务器时间。

这样的事情应该工作:

function syncTime() {
    // Set up our time object, synced by the HTTP DATE header
    // Fetch the page over JS to get just the headers
    console.log("syncing time")
    var r = new XMLHttpRequest();
    var start = (new Date).getTime();

    r.open('HEAD', document.location, false);
    r.onreadystatechange = function()
    {
        if (r.readyState != 4)
        {
            return;
        }
        var latency = (new Date).getTime() - start;
        var timestring = r.getResponseHeader("DATE");

        // Set the time to the **slightly old** date sent from the 
        // server, then adjust it to a good estimate of what the
        // server time is **right now**.
        systemtime = new Date(timestring);
        systemtime.setMilliseconds(systemtime.getMilliseconds() + (latency / 2))
    };
    r.send(null);
}

有趣的是:John Resig 有一篇关于 Javascript 计时准确性的好文章
在这种情况下它不应该引起问题,因为您只关心您的时间差了大约 1 秒。15 毫秒的差异应该不会产生太大影响。

有没有比将响应时间除以 2 更好的选择?在我的一些测试中,服务器会阻塞一段时间,但很快就会返回。所以也许跳闸时间是 200 毫秒,但返回的时间只有 20 毫秒。那有意义吗?
2021-04-18 06:30:36
有用的代码。它提供了比“ServerDate”库更好的结果。:)
2021-04-30 06:30:36
这就是我要做的。唯一的区别是我会进行多次同步调用(即 10 次),然后使用具有最低延迟的调用中的系统时间。事实上,延迟越高,您受到的影响就越大,因为从客户端到服务器时间和从服务器到客户端时间之间的分布并不完全是 1:1。
2021-05-06 06:30:36
一个惊人的答案!很确定这是可用的最佳解决方案,但我将增加悬赏,看看我是否可以吸引其他一些解决方案。
2021-05-09 06:30:36

换一种不同的方法怎么样:谁在乎时间?(您不会使用 JavaScript 可靠地同步系统时钟。)

相反,当您的客户端推进幻灯片放映时,使用带有socket.io节点服务器进行同步。不是客户端决定何时前进,而是服务器告诉他们前进。

这种方法带来了额外的好处,即能够在幻灯片放映时手动摆弄它。在下面的示例中,我添加了一个Next按钮,该按钮使所有连接的客户端立即前进到下一张幻灯片。

应用程序.js

var express = require('express')
    , app = express.createServer()
    , io = require('socket.io').listen(app)
    , doT = require('dot')
    , slide = 0
    , slides = [
        'http://placekitten.com/700/400?image=13',
        'http://placekitten.com/700/400?image=14',
        'http://placekitten.com/700/400?image=15',
        'http://placekitten.com/700/400?image=16',
        'http://placekitten.com/700/400?image=1',
        'http://placekitten.com/700/400?image=2',
        'http://placekitten.com/700/400?image=3',
        'http://placekitten.com/700/400?image=4',
        'http://placekitten.com/700/400?image=5',
        'http://placekitten.com/700/400?image=6',
        'http://placekitten.com/700/400?image=7',
        'http://placekitten.com/700/400?image=8',
        'http://placekitten.com/700/400?image=9',
        'http://placekitten.com/700/400?image=10',
        'http://placekitten.com/700/400?image=11',
        'http://placekitten.com/700/400?image=12',
    ];

app.listen(70); // listen on port 70

app.register('.html', doT); // use doT to render templates
app.set('view options', {layout:false}); // keep it simple
doT.templateSettings.strip=false; // don't strip line endings from template file

app.get('/', function(req, res) {
    res.render('index.html', { slide: slide, slides: slides });
});

app.post('/next', function(req, res) {
    next();
    res.send(204); // No Content
});

setInterval(next, 4000); // advance slides every 4 seconds

function next() {
    if (++slide >= slides.length) slide = 0;
    io.sockets.emit('slide', slide);
}

视图/index.html

此文件作为doT模板处理。

<!DOCTYPE html>
<html>
<head>
<title>Synchronized Slideshow</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script>
var curslide = {{=it.slide}}; // the slide the server is currently on.

$(function() {
    $('#slide' + curslide).css('left',0);

    $('#next').click(function() {
        $.post('/next');
    });
});

var socket = io.connect('http://localhost:70');
socket.on('slide', function(slide) {
    $('#slide' + curslide).animate({left:-700}, 400);
    $('#slide' + slide).css('left',700).show().animate({left:0}, 400);
    curslide = slide;
});
</script>
<style>
#slideshow, .slide { width:700px; height:400px; overflow:hidden; position:relative; }
.slide { position:absolute; top:0px; left:700px; }
</style>
</head>
<body>
    <div id="slideshow">
        {{~it.slides :url:i}}
            <div id="slide{{=i}}" class="slide"><img src="{{=url}}"></div>
        {{~}}
    </div>
    <button id="next">Next &gt;</button>
</body>
</html>

将这两个文件复制到一个文件夹中,然后运行

$ npm install express socket.io dot
$ node app.js

并导航到http://localhost:70几个不同的窗口,然后看看魔术。

是的,这已经很老了,而且 express 在 v2 和 v4 之间显然有很多重大变化。需要sudo来自侦听端口 70,将其更改为 >1024 以修复。
2021-04-25 06:30:36
喜欢这个答案!忽略我的问题以达到相同的目标。我将采用这种方法,看看我能从中得到什么!
2021-04-29 06:30:36
使用最新版本的 Express 运行此程序时遇到了一些错误。只有当我将它固定到 2.5.10 版并重新安装时它才起作用。另外,我不得不跑sudo node app.js
2021-04-29 06:30:36

很高兴您为您的问题找到了满意的答案。我也有类似的需求,需要将浏览器与服务器的时钟同步,并决心像您一样以超过 1 秒的精度实现它。我已经编写了代码来执行此操作,并在此处发布此答案,以防其他人也需要该解决方案。

该代码称为ServerDate,可免费下载。这是自述文件的一部分。请注意,我在示例中达到了 108 毫秒的精度:

您可以ServerDate像使用该Date函数或其实例之一一样使用,例如:

> ServerDate()
"Mon Aug 13 2012 20:26:34 GMT-0300 (ART)"

> ServerDate.now()
1344900478753

> ServerDate.getMilliseconds()
22

还有一种新方法可以获取 ServerDate 对服务器时钟的估计精度(以毫秒为单位):

> ServerDate.toLocaleString() + " ± " + ServerDate.getPrecision() + " ms"
"Tue Aug 14 01:01:49 2012 ± 108 ms"

您可以看到服务器时钟和浏览器时钟之间的差异,以毫秒为单位:

> ServerDate - new Date()
39

您无法真正与服务器同步。测量您的服务器请求所需的时间(如 MikeWyatt 建议的)并不是延迟的好指标。

只有您的服务器知道他何时响应请求。因此,它应该将该信息与答案一起发回。有了Date.now() - new Date(timestringOfServerResponse)你可以精确地测量延迟。但我不确定你为什么需要那个值。

要在多个设备之间同步应用程序,服务器应该向它们发送要执行的操作。“何时”不应该是“一旦你得到我的回应”,而是一个确切的时间戳。只要您设备的系统时钟是准确和同步的(它们通常是),该应用程序将同步运行其方法,因为它知道何时会发生什么(或至少:当时应该发生什么,并且它可以插入什么发生“现在”)。

@Bergi,您错误地假设客户端的时钟与服务器上的时钟相匹配。它们实际上可以保证不同,可能相差几秒或几分钟。调整延迟并不完美,但它会让你非常接近。在最坏的情况下,如果其中一次旅行是即时的,您将在整个往返时间前离开。
2021-04-18 06:30:36
所以你想在javascript中实现一个时间服务器来同步“自定义系统时间”?
2021-04-29 06:30:36
这正是它的运作方式。在执行操作之前,页面会等待正确的时间,以确保它在设备之间同步。问题归结为我有两个相邻的 iPad,它们设置为与时间服务器同步时间,但相距 1 秒。
2021-05-03 06:30:36

我在这里广泛地将 COMET 模式用于我的实时 Web 应用程序。

要在您的情况下使用它,您需要客户端向服务器打开 AJAX 请求并等待答案。一旦出现,客户就必须更换幻灯片。

在服务器上,您必须保留所有答案,直到需要更换幻灯片。(您可以更先进,然后在客户端上延迟每个相同的时间,但这很可能没有必要)。我无法在这里向您展示示例代码,因为我不知道您可以使用什么。

因此,您正在有效地创建一个管弦乐队,其中服务器演奏指挥而所有客户端都在听他的演奏。

然后由服务器在(几乎)同一时间响应请求的能力加上网络延迟来确定时间。
通常客户端应该在网络的同一部分,所以延迟可能非常相似 - 绝对值在这里不会受到影响,只有变化。

并且可能还有一个额外的技巧有帮助:不要用硬替换来改变幻灯片,而是将它们混合。这将模糊更改,使眼睛无法捕捉到您始终拥有的微小时间差异。

(如果你不能让服务器扮演指挥你可能不得不使用 MikeWyatt 的解决方案 - 可能有几个请求并平均结果,具体取决于网络设置。在 LAN 中,一个请求可能就足够了,去在整个互联网上稍微超过平均不会伤害......)