4

サーバー上の大量の JPG ファイルのセットを読み取り (同じディレクトリに JPG ファイルを書き込み続ける別のプロセスがあるため無限)、それらを一定の時間間隔で MJPEG ストリームとしてユーザーのブラウザーに送信するスクリプトを作成しています (以下のコードの変数「frameDelay」)。これは、IP カメラが行うことと似ています。

このスクリプトのメモリ使用量が増え続け、常にシステムによって強制終了されることがわかりました (Ubuntu)。

この一見単純なスクリプトを何度も調べました。したがって、私は以下のコードを投稿しています。コメント/提案は大歓迎です!

app.get('/stream', function (req, res) {
    res.writeHead(200, {
        'Content-Type':'multipart/x-mixed-replace;boundary="' + boundary + '"',
        'Transfer-Encoding':'none',
        'Connection':'keep-alive',
        'Expires':'Fri, 01 Jan 1990 00:00:00 GMT',
        'Cache-Control':'no-cache, no-store, max-age=0, must-revalidate',
        'Pragma':'no-cache'
    });
res.write(CRLF + "--" + boundary + CRLF);

setInterval(function () {
    if(fileList.length<=1){
        fileList = fs.readdirSync(location).sort();
    }else{
        var fname = fileList.shift();
        if(fs.existsSync(location+fname)){
           var data = fs.readFileSync(location+fname);
            res.write('Content-Type:image/jpeg' + CRLF + 'Content-Length: ' + data.length + CRLF + CRLF);
            res.write(data);
            res.write(CRLF + '--' + boundary + CRLF);                   
            fs.unlinkSync(location+fname);
        }else{
            console.log("File doesn't find")
        }
    }
        console.log("new response:" + fname);
    }, frameDelay);
});

app.listen(port);
console.log("Server running at port " + port);

トラブルシューティング プロセスを容易にするために、以下はスタンドアロン (サードパーティ ライブラリなし) のテスト ケースです。

まったく同じメモリの問題があります(メモリ使用量が増え続け、最終的にOSによって強制終了されました)。

問題は setInterval () ループにあると考えられます。おそらく、これらの画像は送信後にメモリから削除されていないか、何か (変数 "res" にまだ保存されている可能性があります)。

フィードバックや提案は大歓迎です!

var http                = require('http');
var fs                  = require('fs');

var framedelay  = 40;
var port                = 3200;
var boundary    = 'myboundary';
var CR                  = '\r';
var LF                  = '\n';
var CRLF                = CR + LF;

function writeHttpHeader(res)
{
        res.writeHead(200,
        {
                'Content-Type': 'multipart/x-mixed-replace;boundary="' + boundary + '"',
                'Transfer-Encoding': 'none',
                'Connection': 'keep-alive',
                'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT',
               'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
                'Pragma': 'no-cache',
        });

        res.write(CRLF + '--' + boundary + CRLF);
}

function writeJpegFrame(res, filename)
{
        fs.readFile('./videos-8081/frames/' + filename, function(err, data)
        {
                if (err)
                {
                        console.log(err);
                }
                else
                {
                        res.write('Content-Type:image/jpeg' + CRLF);
                        res.write('Content-Length:' + data.length + CRLF + CRLF);
                            res.write(data);
                        res.write(CRLF + '--' + boundary + CRLF);
                        console.log('Sent ' + filename);
                }
        });
}
http.createServer(function(req, res)
{
     writeHttpHeader(res)    
     fs.readdir('./videos-8081/frames', function(err, files)
     { 
         var i = -1;
         var sorted_files = files.sort();    
         setInterval(function()
         {
              if (++i >= sorted_files.length)
              {
                i = 0;
              }    
              writeJpegFrame(res, sorted_files[i]);
          }, framedelay);
        });
}).listen(port);
console.log('Server running at port ' + port);                    
4

2 に答える 2

4

もちろん、メモリリークします。あなたがやる

 setInterval(...)

ただし、これらの間隔を消去することはありません。つまり、(たとえば) 20 のリクエストの後、バックグラウンドで 20 の間隔が実行され、クライアント/接続が長期間停止していても、永久に実行されます。解決策の1つは次のとおりです。

var my_interval = setInterval(function() {
    try {
        // all your code goes here
    } catch(e) {
        // res.write should throw an exception once the connection is dead
        // do the cleaning now
        clearInterval( my_interval );
    }
}, frameDelay);

req.on( "close", function() {
    // just in case
    clearInterval( my_interval );
});

これによりmy_interval、接続が閉じられると (および対応するすべてのデータ) が消去されます。

PSファイルの読み込みに時間がかかる場合があり、問題が発生する可能性があるため、setTimeout代わりにを使用することをお勧めします。setIntervalframeDelay

PS2。関数の非同期バージョンを使用しますfs。Node.JS のすべての力はノンブロッキング操作にあり、ここで主な利点 (パフォーマンス) を失っています。

于 2012-11-12T22:44:54.377 に答える
2

これにはいくつかの原因があります

  • MJPEG は、高解像度の動画を高頻度で送信するようには設計されていません (この場合、25fps は 320x240 フレームでは問題ないかもしれませんが、720p では問題ないかもしれません)。ペイロードの出力スループット 25fps*70KB = 1750KBps = 14Mbps を考慮してください。フル HD ビデオよりも高くなります。
  • クライアントが受信できない場合、Node.js は出力をバッファーにキャッシュします。クライアントに大量のデータを送信しているため、ノードはそれらを保存しました。これが、メモリ使用量が決して下がらない理由であり、メモリ リークではありません。出力バッファが大きくなっているかどうかを検出するには、 の戻り値をチェックしますres.write()
  • setInterval()使用しても問題なく、クライアントが接続を維持している限り問題は発生しません。ただし、クライアントが切断されたら、停止する必要があります。そのためには、'close' イベントを監視する必要があります。
  • MJPEG はこの目的のために設計されていないため、安定した fps を維持することはできず、いくら頑張ってもクライアントで fps を制御することはできません。しかし、慎重に設計されたコードを使用すると、 を使用して平均 fpsを安定させることができますsetTimeout()

これが固定コードです。

var http = require('http');
var fs = require('fs');
var framedelay = 40;
var port = 3200;
var boundary = 'myboundary';
var CR = '\r';
var LF = '\n';
var CRLF = CR + LF;

http.createServer(function(req, res) {

  var files = fs.readdirSync('./imgs');
  var i = -1;
  var timer;
  var sorted_files = files.sort();

  res.writeHead(200, {
    'Content-Type': 'multipart/x-mixed-replace;boundary="' + boundary + '"',
    'Transfer-Encoding': 'none',
    'Connection': 'keep-alive',
    'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT',
    'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
    'Pragma': 'no-cache',
  });

  res.write(CRLF + '--' + boundary + CRLF);

  var writePic = function() {

    if (++i >= sorted_files.length)
      i = 0;

    var data = fs.readFileSync('./imgs/' + sorted_files[i]);
    res.write('Content-Type:image/jpeg' + CRLF);
    res.write('Content-Length:' + data.length + CRLF + CRLF);
    res.write(data);
    var ok = res.write(CRLF + '--' + boundary + CRLF);
    console.log('Sent ' + sorted_files[i], ok);

    if (ok)
      timer = setTimeout(writePic, framedelay);
  };

  res.on('close', function() {
    console.log('client closed');
    clearTimeout(timer);
  });

  res.on('drain', function() {
    console.log('drain');
    timer = setTimeout(writePic, framedelay);
  });

}).listen(port);

console.log('Server running at port ' + port);
于 2012-11-13T03:32:14.370 に答える