为什么fs.createReadStream ... pipe(res)锁定了读取文件?

时间:2016-08-20 02:31:23

标签: node.js express locking video-streaming fs

我正在使用express来传输音频和音频视频文件according to this answer。相关代码如下所示:

function streamMedia(filePath, req, res) {
  // code here to determine which bytes to send, compute response headers, etc.

  res.writeHead(status, headers);
  var stream = fs.createReadStream(filePath, { start, end })
    .on('open', function() {
      stream.pipe(res);
    })
    .on('error', function(err) {
      res.end(err);
    })
  ;
}

这适用于将字节流式传输到客户端上的<audio><video>元素。但是,在提供这些请求之后,另一个快速请求可以删除从文件系统流式传输的文件。第二个请求失败了。

只要文件至少流式传输一次(意味着在运行上面的代码时为文件的路径调用了createReadStream),会发生一个不同的快速请求来删除文件,文件系统保留在文件系统上,直到express停止。一旦express停止,文件就会从文件系统中删除。

这到底发生了什么?锁定文件的原因是fs还是express,原因是什么,以及如何让进程释放文件以便删除它(在其内容被读取并传送到响应之后) ,如果还有待处理)?

更新1:

我修改了上面的代码,为第二个函数arg设置了autoClose: true,并添加了'end''close'个事件处理程序,如下所示:

res.writeHead(status, headers);
var streamReadOpts = { start: start, end: end, autoClose: true };
var stream = fs.createReadStream(filePath, streamReadOpts)
    // previous 'open' & 'error' event handlers are still here
    .on('end', function () {
      console.log('stream end');
    })
    .on('close', function () {
      console.log('stream close');
    })

我发现,当网页最初加载<video><audio>元素时,只会触发'open'偶数。然后,当用户点击播放视频/音频时,会发出第二个请求,第二次发出'end''close'个事件,然后删除文件成功。

因此,当用户从调用此函数的请求加载具有<video><audio>元素并获取其source的页面时,该文件似乎被锁定。直到该媒体文件被播放,才发出第二个请求,并且文件被解锁。

我还发现关闭浏览器还会导致'end''close'事件触发,并且文件将被解锁。我的猜测是我对快递res做了一些错误,使其不能正常关闭,但我仍然不确定那是什么。

2 个答案:

答案 0 :(得分:3)

事实证明,解决方案是在每次请求期间从文件中读取并传输较小的数据块。在我的测试用例中,我正在传输一个6MB的MP4视频文件。虽然我能够使用firefox或chrome重现问题,但我使用后者调试,发现客户端阻止了流

当页面最初加载时,有一个看起来像这样的元素:

<video> <!-- or <audio> -->
    <source src="/path/to/express/request" type="video/mpeg" /> <!-- or audio/mpeg -->
</video> <!-- or </audio> -->

正如OP中引用的其他答案所述,chrome会发送一个带有范围标题的请求,如下所示:

Range:bytes=0-

对于这个请求,我的函数是发送整个文件,我的响应看起来像这样:

Accept-Ranges:bytes
Connection:keep-alive
Content-Length:6070289
Content-Range:bytes 0-6070288/6070289
Content-Type:video/mp4

然而, chrome并没有阅读整篇文章。它只读取前3-4MB,然后阻止连接,直到用户操作导致它需要文件的其余部分。这解释了为什么关闭浏览器或停止快递导致文件被解锁,因为它关闭了浏览器或服务器端的连接。

我目前的解决方案是一次只能发送最多1MB(旧学校1MB,1024 * 1024)块。相关代码可在an additional answer to the question referenced in the OP

中找到

答案 1 :(得分:1)

在选项中设置autoClose = true。如果autoClose = false,则必须在'end'事件中手动关闭它。

请参阅节点文档: - https://nodejs.org/api/fs.html#fs_fs_createreadstream_path_options