使用节点的多客户端长轮询处理

multi-client long poll handling with node

我正在尝试创建一个节点服务器,它会在服务器上更新文件时通知长轮询客户端。但是,我似乎无法想出让服务器识别文件中的更改并更新已执行更新的任何轮询客户端的代码。

我的困惑根源在于时间戳的处理,具体来说,我应该跟踪哪些时间戳。客户只想知道文件是否已更改。我认为我们不必关心请求何时进入,即我们不必存储请求的时间戳来确定对文件的更改。但是,我们需要跟踪以确定请求何时过期。

所以,我认为服务器收到请求并立即存储目标文件的时间戳。然后,服务器每 10 秒检查一次存储的时间戳与当前文件的时间戳。如果存在差异,则文件已更新,服务器会向客户端发送一个响应,指示文件已更改。如果服务器在 60 秒后没有看到文件发生变化,它会向客户端发送一个响应,指示客户端应该发起一个新的请求。

上面的策略有意义吗?如何有效地处理时间戳?而且,一个人如何同时处理多个客户?还有,如何防止服务器被同一客户端的多个请求淹没?

您需要注意客户端发起新请求时发生的情况,因为在此期间文件可能会更改 window。

解决这个问题的一种方法是让客户端首先查询当前文件状态:

GET /file/timestamp
     -> Server returns the timestamp

GET /file/update?after=[timestamp]
     -> Server checks whether the file has changed after the timestamp.
        If it has, the server sends the response immediately.
        Otherwise insert the client into a queue.
        You don't need to save the timestamp in the queue.
        If the server notices a change it notifies the clients.

现在由于有多个客户端,服务器不应在客户端请求处理程序中进行轮询。取而代之的是有一个单独的对象来处理轮询。

根据您是否有一个或多个文件需要监视,您最终可能会为此采用简单或复杂的实现方式。简而言之,尽管您可能想要包装 fs.watchFile in an EventEmitter 以便对文件的更改将发出 change-events.

一个天真的实现是:

var watcher = new EventEmitter();

// Get the initial status
fs.lstat( 'test.file', function( err, stat ) {
    if( err ) return console.error( err );
    watcher.stat = stat;
});

// Start watching the file and emit an event whenever the file changes.
fs.watchFile( 'test.file', function( curr, prev ) {
    console.log( 'File changed' );
    watcher.stat = curr;
    watcher.emit( 'change', curr );
});

有了这些,您的请求处理程序将类似于:

var server = http.createServer( function( req, res ) {

    res.writeHead( 200, { 'Content-Type': 'text/html' });

    var timeout;
    var onChange = function( stat ) {
        // Prevent timeout from triggering
        clearTimeout( timeout );

        console.log( 'File changed at ' + stat.mtime );
        res.end(
            'Changed at ' + stat.mtime + '. ' +
            '<a href="?after=' + stat.mtime.getTime() + '">Poll again</a>' );
    };

    var after = url.parse( req.url ).query || '';
    after = after.split('=');
    console.dir( after );
    if( after.length < 2 || after[1] < watcher.stat.mtime.getTime() ) {
        console.log( 'Initial request or file changed before request' );
        return onChange( watcher.stat );
    }

    console.log( 'Polling request.' );

    watcher.once( 'change', onChange );
    timeout = setTimeout( function() {
        console.log( 'File not changed. Giving up.' );
        watcher.removeListener( 'change', onChange );
        res.end(
            'Unchanged! <a href="?after=' + after[1] + '">Poll again</a>' );
    }, 10000 );
});

最后 "prevent the server from being overrun by multiple requests from the same client?" - 你不知道。如果您想保证这一点并仍然允许匿名请求,则不会。您可以尝试基于 cookie 的排除,但如果您的服务允许匿名请求,则用户可以停止发送 cookie,此时很难识别来自同一浏览器的请求。