Javascript 使用Node.js实时读取文件

Javascript 使用Node.js实时读取文件,javascript,node.js,real-time,fifo,unix-socket,Javascript,Node.js,Real Time,Fifo,Unix Socket,我需要使用node.js找出实时读取写入文件的数据的最佳方法。问题是,Node是一艘快速移动的船,这使得寻找解决问题的最佳方法变得困难 我想做什么 我有一个java进程,它正在做一些事情,然后将它所做的事情的结果写入一个文本文件。它通常需要5分钟到5小时的时间来运行,整个时间都在写入数据,并且可以达到相当高的吞吐量(大约1000行/秒) 我希望实时读取此文件,然后使用node聚合数据并将其写入套接字,以便在客户端上对其进行图形化 客户机、图形、套接字和聚合逻辑都完成了,但我对读取文件的最佳方法感

我需要使用node.js找出实时读取写入文件的数据的最佳方法。问题是,Node是一艘快速移动的船,这使得寻找解决问题的最佳方法变得困难

我想做什么
我有一个java进程,它正在做一些事情,然后将它所做的事情的结果写入一个文本文件。它通常需要5分钟到5小时的时间来运行,整个时间都在写入数据,并且可以达到相当高的吞吐量(大约1000行/秒)

我希望实时读取此文件,然后使用node聚合数据并将其写入套接字,以便在客户端上对其进行图形化

客户机、图形、套接字和聚合逻辑都完成了,但我对读取文件的最佳方法感到困惑

我尝试过的(或至少玩过的)
FIFO
-我可以告诉我的Java进程使用节点写入FIFO并读取它,事实上这就是我们目前使用Perl实现它的方式,但是因为其他所有东西都在节点中运行,所以将代码移植过来是有意义的

Unix套接字
-如上所述

fs.watchFile
-这能满足我们的需要吗

fs.createReadStream
-这比watchFile好吗

fs
&
tail-f
-看起来像是一个黑客

实际上,我的问题是什么
我倾向于使用Unix套接字,这似乎是最快的选择。但是node是否有更好的内置功能来实时读取fs中的文件呢?

为什么您认为
tail-f
是一种黑客行为

虽然我发现了一个很好的例子,但我会做类似的事情。 node.js和WebSocket的实时在线活动监视器示例:

为了完整地回答这个问题,我编写了一个在0.8.0(http服务器可能是黑客)下运行的示例代码

子进程是用tail生成的,因为子进程是一个具有三个流的EventEmitter(在我们的例子中使用stdout),所以您只需添加一个启用了
的侦听器

文件名:tailServer.js

用法:
node-tailServer/var/log/filename.log

var http = require("http");
var filename = process.argv[2];


if (!filename)
    return console.log("Usage: node tailServer filename");

var spawn = require('child_process').spawn;
var tail = spawn('tail', ['-f', filename]);

http.createServer(function (request, response) {
    console.log('request starting...');

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

    tail.stdout.on('data', function (data) {
      response.write('' + data);                
    });
}).listen(8088);

console.log('Server running at http://127.0.0.1:8088/');

如果您希望将文件作为数据的永久存储,以防止在系统崩溃或正在运行的进程网络中的一个成员死亡时丢失流,您仍然可以继续写入文件并从中读取

如果您不需要将此文件作为Java进程生成的结果的持久存储,那么使用Unix套接字在方便性和性能方面都会更好

fs.watchFile()
不是您所需要的,因为它在文件系统报告文件状态时对文件状态起作用,并且由于您希望在文件已被写入时读取该文件,因此这不是您所需要的

简短更新:我很遗憾地意识到,尽管我在上一段中指责
fs.watchFile()
使用了文件统计信息,但我自己在下面的示例代码中也做了同样的事情!虽然我已经警告读者要“小心”,因为我只用了几分钟就写好了,甚至连测试都不好;不过,如果底层系统支持,可以使用
fs.watch()
而不是
watchFile
fstatSync
,这样做会更好

为了从文件中读/写,我刚刚在休息时写了以下内容:

测试fs writer.js:[您将不需要它,因为您在Java进程中编写文件]

var fs = require('fs'),
    lineno=0;

var stream = fs.createWriteStream('test-read-write.txt', {flags:'a'});

stream.on('open', function() {
    console.log('Stream opened, will start writing in 2 secs');
    setInterval(function() { stream.write((++lineno)+' oi!\n'); }, 2000);
});
测试fs reader.js:[注意,这只是演示,检查错误对象!]

var fs = require('fs'),
    bite_size = 256,
    readbytes = 0,
    file;

fs.open('test-read-write.txt', 'r', function(err, fd) { file = fd; readsome(); });

function readsome() {
    var stats = fs.fstatSync(file); // yes sometimes async does not make sense!
    if(stats.size<readbytes+1) {
        console.log('Hehe I am much faster than your writer..! I will sleep for a while, I deserve it!');
        setTimeout(readsome, 3000);
    }
    else {
        fs.read(file, new Buffer(bite_size), 0, bite_size, readbytes, processsome);
    }
}

function processsome(err, bytecount, buff) {
    console.log('Read', bytecount, 'and will process it now.');

    // Here we will process our incoming data:
        // Do whatever you need. Just be careful about not using beyond the bytecount in buff.
        console.log(buff.toString('utf-8', 0, bytecount));

    // So we continue reading from where we left:
    readbytes+=bytecount;
    process.nextTick(readsome);
}

本模块实现了@hasanyasin建议的原则:


我从@hasanyasin那里得到了答案,并将其包装成一个模块化的承诺。基本思想是传递一个文件和一个处理函数,该函数使用从文件读取的字符串化缓冲区执行某些操作。如果处理程序函数返回true,则文件将停止读取。您还可以设置一个超时,如果处理程序返回true的速度不够快,该超时将终止读取

如果由于超时而调用了resolve(),promiser将返回true,否则将返回false

有关用法示例,请参见底部

// https://stackoverflow.com/a/11233045

var fs = require('fs');
var Promise = require('promise');

class liveReaderPromiseMe {
    constructor(file, buffStringHandler, opts) {
        /*
            var opts = {
                starting_position: 0,
                byte_size: 256,
                check_for_bytes_every_ms: 3000,
                no_handler_resolution_timeout_ms: null
            };
        */

        if (file == null) {
            throw new Error("file arg must be present");
        } else {
            this.file = file;
        }

        if (buffStringHandler == null) {
            throw new Error("buffStringHandler arg must be present");
        } else {
            this.buffStringHandler = buffStringHandler;
        }

        if (opts == null) {
            opts = {};
        }

        if (opts.starting_position == null) {
            this.current_position = 0;
        } else {
            this.current_position = opts.starting_position;
        }

        if (opts.byte_size == null) {
            this.byte_size = 256;
        } else {
            this.byte_size = opts.byte_size;
        }

        if (opts.check_for_bytes_every_ms == null) {
            this.check_for_bytes_every_ms = 3000;
        } else {
            this.check_for_bytes_every_ms = opts.check_for_bytes_every_ms;
        }

        if (opts.no_handler_resolution_timeout_ms == null) {
            this.no_handler_resolution_timeout_ms = null;
        } else {
            this.no_handler_resolution_timeout_ms = opts.no_handler_resolution_timeout_ms;
        }
    }


    startHandlerTimeout() {
        if (this.no_handler_resolution_timeout_ms && (this._handlerTimer == null)) {
            var that = this;
            this._handlerTimer = setTimeout(
                function() {
                    that._is_handler_timed_out = true;
                },
                this.no_handler_resolution_timeout_ms
            );
        }
    }

    clearHandlerTimeout() {
        if (this._handlerTimer != null) {
            clearTimeout(this._handlerTimer);
            this._handlerTimer = null;
        }
        this._is_handler_timed_out = false;
    }

    isHandlerTimedOut() {
        return !!this._is_handler_timed_out;
    }


    fsReadCallback(err, bytecount, buff) {
        try {
            if (err) {
                throw err;
            } else {
                this.current_position += bytecount;
                var buff_str = buff.toString('utf-8', 0, bytecount);

                var that = this;

                Promise.resolve().then(function() {
                    return that.buffStringHandler(buff_str);
                }).then(function(is_handler_resolved) {
                    if (is_handler_resolved) {
                        that.resolve(false);
                    } else {
                        process.nextTick(that.doReading.bind(that));
                    }
                }).catch(function(err) {
                    that.reject(err);
                });
            }
        } catch(err) {
            this.reject(err);
        }
    }

    fsRead(bytecount) {
        fs.read(
            this.file,
            new Buffer(bytecount),
            0,
            bytecount,
            this.current_position,
            this.fsReadCallback.bind(this)
        );
    }

    doReading() {
        if (this.isHandlerTimedOut()) {
            return this.resolve(true);
        } 

        var max_next_bytes = fs.fstatSync(this.file).size - this.current_position;
        if (max_next_bytes) {
            this.fsRead( (this.byte_size > max_next_bytes) ? max_next_bytes : this.byte_size );
        } else {
            setTimeout(this.doReading.bind(this), this.check_for_bytes_every_ms);
        }
    }


    promiser() {
        var that = this;
        return new Promise(function(resolve, reject) {
            that.resolve = resolve;
            that.reject = reject;
            that.doReading();
            that.startHandlerTimeout();
        }).then(function(was_resolved_by_timeout) {
            that.clearHandlerTimeout();
            return was_resolved_by_timeout;
        });
    }
}


module.exports = function(file, buffStringHandler, opts) {
    try {
        var live_reader = new liveReaderPromiseMe(file, buffStringHandler, opts);
        return live_reader.promiser();
    } catch(err) {
        return Promise.reject(err);
    }
};
然后像这样使用上述代码:

var fs = require('fs');
var path = require('path');
var Promise = require('promise');
var liveReadAppendingFilePromiser = require('./path/to/liveReadAppendingFilePromiser');

var ending_str = '_THIS_IS_THE_END_';
var test_path = path.join('E:/tmp/test.txt');

var s_list = [];
var buffStringHandler = function(s) {
    s_list.push(s);
    var tmp = s_list.join('');
    if (-1 !== tmp.indexOf(ending_str)) {
        // if this return never occurs, then the file will be read until no_handler_resolution_timeout_ms
        // by default, no_handler_resolution_timeout_ms is null, so read will continue forever until this function returns something that evaluates to true
        return true;
        // you can also return a promise:
        //  return Promise.resolve().then(function() { return true; } );
    }
};

var appender = fs.openSync(test_path, 'a');
try {
    var reader = fs.openSync(test_path, 'r');
    try {
        var options = {
            starting_position: 0,
            byte_size: 256,
            check_for_bytes_every_ms: 3000,
            no_handler_resolution_timeout_ms: 10000,
        };

        liveReadAppendingFilePromiser(reader, buffStringHandler, options)
        .then(function(did_reader_time_out) {
            console.log('reader timed out: ', did_reader_time_out);
            console.log(s_list.join(''));
        }).catch(function(err) {
            console.error('bad stuff: ', err);
        }).then(function() {
            fs.closeSync(appender);
            fs.closeSync(reader);
        });

        fs.write(appender, '\ncheck it out, I am a string');
        fs.write(appender, '\nwho killed kenny');
        //fs.write(appender, ending_str);
    } catch(err) {
        fs.closeSync(reader);
        console.log('err1');
        throw err;
    }
} catch(err) {
    fs.closeSync(appender);
        console.log('err2');
    throw err;
}

我对tail-f的担心是,如果不是因为数据丢失,它要求在将文件写入之前,读取进程处于活动状态。我的用例是这样的:读取可能在数据写入之后很久才会发生+1用于0.8的更新,尽管这是一个很好的解决方案,用于从同一源控制写入和读取。watchFile也是事件驱动的,但根据文档,NOT是稳定的。上面的示例通过轮询高级代码来更改handels文件。对我来说,这看起来像一个黑客。但只要它对你有用,这样做很好。否则,如果文件不存在,您可以触摸该文件,并且不会丢失任何数据,您可以使用
wc-l message.text | awk'{print$1}'
将其交给
tail-f-n
我认为,该代码在windows机器上不起作用。我还没有在windows上测试过它。虽然tail应该在Win10上工作,否则WSL将无法工作。但现在有更好的解决方案。也没有可用且维护良好的依赖尾部npm。这是一个直接解决我的问题的好例子。虽然一次只处理一条生产线需要改进,但可以说这是一件好事;node缺少现有的fs接口,这意味着它是完全可定制的,所以即使我必须编写额外的代码,我也可以完全实现我所需要的。这在作为node运行时绝对有效,但我如何才能将此代码放入app.js并在html页面中获得结果?谢谢,这看起来在这里和felixge都能很好地工作
var fs = require('fs');
var path = require('path');
var Promise = require('promise');
var liveReadAppendingFilePromiser = require('./path/to/liveReadAppendingFilePromiser');

var ending_str = '_THIS_IS_THE_END_';
var test_path = path.join('E:/tmp/test.txt');

var s_list = [];
var buffStringHandler = function(s) {
    s_list.push(s);
    var tmp = s_list.join('');
    if (-1 !== tmp.indexOf(ending_str)) {
        // if this return never occurs, then the file will be read until no_handler_resolution_timeout_ms
        // by default, no_handler_resolution_timeout_ms is null, so read will continue forever until this function returns something that evaluates to true
        return true;
        // you can also return a promise:
        //  return Promise.resolve().then(function() { return true; } );
    }
};

var appender = fs.openSync(test_path, 'a');
try {
    var reader = fs.openSync(test_path, 'r');
    try {
        var options = {
            starting_position: 0,
            byte_size: 256,
            check_for_bytes_every_ms: 3000,
            no_handler_resolution_timeout_ms: 10000,
        };

        liveReadAppendingFilePromiser(reader, buffStringHandler, options)
        .then(function(did_reader_time_out) {
            console.log('reader timed out: ', did_reader_time_out);
            console.log(s_list.join(''));
        }).catch(function(err) {
            console.error('bad stuff: ', err);
        }).then(function() {
            fs.closeSync(appender);
            fs.closeSync(reader);
        });

        fs.write(appender, '\ncheck it out, I am a string');
        fs.write(appender, '\nwho killed kenny');
        //fs.write(appender, ending_str);
    } catch(err) {
        fs.closeSync(reader);
        console.log('err1');
        throw err;
    }
} catch(err) {
    fs.closeSync(appender);
        console.log('err2');
    throw err;
}