我正在尝试为Node.js实现一个例程,该例程将允许打开一个文件,该文件此时正由其他进程附加,然后在将数据附加到文件时立即返回大块数据。可以认为它与tail -f UNIX命令相似,但是可以立即使用,只要有可用的块即可,而不是轮询一段时间内的更改。另外,您可以将其视为与使用套接字一样处理文件—期望on('data')会不时触发,直到明确关闭文件为止。

在C land中,如果要实现此功能,则只需打开文件,将其文件描述符输入select()(或任何具有类似名称的替代功能),然后在文件描述符标记为“可读”时读取块。因此,当没有什么要读取的内容时,它将无法读取,而当文件中附加了某些内容时,它将再次变得可读。

对于Javascript中的以下代码示例,我有些期待这种行为:

function readThatFile(filename) {
    const stream = fs.createReadStream(filename, {
        flags: 'r',
        encoding: 'utf8',
        autoClose: false // I thought this would prevent file closing on EOF too
    });

    stream.on('error', function(err) {
        // handle error
    });

    stream.on('open', function(fd) {
        // save fd, so I can close it later
    });

    stream.on('data', function(chunk) {
        // process chunk
        // fs.close() if I no longer need this file
    });
}

但是,此代码示例仅会在遇到EOF时解救,因此我不能等待新的块到达。当然,我可以使用fs.openfs.read重新实现此功能,但这在某种程度上违背了Node.js的目的。另外,我可以通过fs.watch()文件进行更改,但是它不能通过网络运行,而且我不喜欢一直重新打开文件而不只是保持打开状态的想法。

我试图做到这一点:
const fd = fs.openSync(filename, 'r'); // sync for readability' sake
const stream = net.Socket({ fd: fd, readable: true, writable: false });

但是没有运气– net.Socket不高兴,并抛出TypeError: Unsupported fd type: FILE

那么,有什么解决方案吗?

UPD:这是不可能的,my answer解释了原因。

最佳答案

我没有研究文件读取流的内部结构,但有可能它们不支持等待文件写入更多数据。但是,fs包绝对支持其最基本的功能。

为了解释拖尾的工作方式,我编写了一个有点古怪的tail函数,该函数将读取整个文件并为每行调用一个回调(仅由\n分隔),然后等待文件中写入更多行。请注意,一种更有效的方法是拥有固定大小的行缓冲区,然后将字节随机混入其中(对于极长的行有特殊情况),而不是修改JavaScript字符串。

var fs = require('fs');

function tail(path, callback) {
  var descriptor, bytes = 0, buffer = new Buffer(256), line = '';

  function parse(err, bytesRead, buffer) {
    if (err) {
      callback(err, null);
      return;
    }
    // Keep track of the bytes we have consumed already.
    bytes += bytesRead;
    // Combine the buffered line with the new string data.
    line += buffer.toString('utf-8', 0, bytesRead);
    var i = 0, j;
    while ((j = line.indexOf('\n', i)) != -1) {
      // Callback with a single line at a time.
      callback(null, line.substring(i, j));
      // Skip the newline character.
      i = j + 1;
    }
    // Only keep the unparsed string contents for next iteration.
    line = line.substr(i);
    // Keep reading in the next tick (avoids CPU hogging).
    process.nextTick(read);
  }

  function read() {
    var stat = fs.fstatSync(descriptor);
    if (stat.size <= bytes) {
      // We're currently at the end of the file. Check again in 500 ms.
      setTimeout(read, 500);
      return;
    }
    fs.read(descriptor, buffer, 0, buffer.length, bytes, parse);
  }

  fs.open(path, 'r', function (err, fd) {
    if (err) {
      callback(err, null);
    } else {
      descriptor = fd;
      read();
    }
  });

  return {close: function close(callback) {
    fs.close(descriptor, callback);
  }};
}

// This will tail the system log on a Mac.
var t = tail('/var/log/system.log', function (err, line) {
  console.log(err, line);
});

// Unceremoniously close the file handle after one minute.
setTimeout(t.close, 60000);

综上所述,您还应该尝试利用NPM社区。经过一些搜索,我发现了tail-stream包,它可以通过流执行您想要的操作。

09-25 17:02
查看更多