пятница, 19 июля 2013 г.

Потоки записи в NodeJS

Потоки записи
Все потоки записи должны реализовывать абстрактный класс stream.Writable.
Данный класс имеет следующую событийную модель:


Можно заметить что stream.Writable имеет событие завершения finish, но stream.Readable имел end. Это для того чтобы обеспечить различие дуплексный поток, одновременный вывод и ввод.
Вот пример некоторых наследников данного класса

              stream.Writable
                 ^            ^
                  |             |
fs.WriteStream      http.ServerResponse
     
 Любой наследник гарантирует, что он предоставит событийную модель родителя. Можно заметить, что данная событийная модель не похожа на модель чтения.

Вот пояснение модели:
1. Мы пробуем записать часть наших данных, если данные записались из буфера сразу в место предназначения в ответ мы получаем тру, можем снова пытаться записать следующую часть.
2. Если же у нас данные из буфера не попали сразу в место предназначения, а стали ожидать своей очереди, то райт вернет фолс. Теперь мы подписуемся на событие потока drain(буфер отдренировался), вместо продолжать заполнять буфер, который рискует в таком случае переполниться, что означает для нас, что мы можем попытаться записать следующую часть, в момент когда, буфер слил все в место предназначения.
3. Когда мы заносим последний чанк данных это следует делать местодм end(...)


Дальше приведем пример, файлового сервера, проблемой для которого является считывание за один раз громадного файла в память, или обслуживание довольно большого количества клиентов с медленным соединением, который не очень быстро принимают передаваемые байты. Все эти варианты заставляют нас рисковать переполнить память нашего сервера.
Поэтому нужно поступать хитрее:
function sendFile(filePath, res) {
    var mime = require('mime').lookup(filePath); // npm install mime

    var file = new fs.ReadStream(filePath);
    file.on('readable', write);

    function write() {
        var fileContent = file.read();

        if (fileContent && !res.write(fileContent)) {//если буфер записи сливается на клинет быстро, мы не попадем в if, а будем и дальше записывать по мере прочтения чанков
            file.removeListener('readable', write); // пока не очищать буфер чтения, его ведь некуда сливать, будем захламлять только память

            res.once('drain', function() {
                file.on('readable', write);
                write();
            });
        }
    }
    file.on('end', function(){//дочитали
        res.end();//значит закрываем соединение с клиентом, мы отправили все что должны были
    });
}

Но в ноде уже есть более оптимизированная реализация данного алгоритма, поэтому выше написанный код, можно заменить на более короткий и оптимальный:
function sendFile(filePath, res) {
    var mime = require('mime').lookup(filePath); // npm install mime

    var file = new fs.ReadStream(filePath);
    file.pipe(res);//pipe есть у всех Readable потоков
    file.pipe(process.stdout);//кроме того пайпить мы можем в любое количество потоков одновременно
    file.on('error', function(err) {
       res.statusCode = 500;
       res.end('Server Error');
       console.log(err)  
    });
    //если оставить код без кода написанного ниже в этом блоке,
    //то в моменты когда клиент будет обрывать соединение,
    // не дождавщись всего файла, у нас закроется соединение записи,
    // но файл чтения останется открытым, держа буферы и замыкания в памяти.
    // при нормальном завершении соедиения, ответ сгенерирует finish, который
    // нам приносит класс stream.Writable.
    res.on('close', function(){//срабатывае, когда соединение обрывают
        file.destroy();//обеспечиваем закрытие файла чтения, и всех контекстов из замыкания  
    });

Комментариев нет:

Отправить комментарий