LoginSignup
5

More than 3 years have passed since last update.

posted at

updated at

Organization

StreamでBuffer.concatしない冴えたやりかた

はじめに

この記事は Node.js Advent Calendar 2019 の 24 日目です。

https://twitter.com/yosuke_furukawa/status/1201778011286065153
Yosuke FURUKAWA @yosuke_furukawa
なんだこの誕生日アドベントカレンダーは / “Node.js Advent Calendar 2019 - Qiita”
午後5:19 · 2019年12月3日·はてなブックマーク

最初は誕生日カレンダーみたいになってたので、ネタ記事を書こうかなーと思っていました。
ですが、始まってみると案外真面目な記事ばかりだったので、真面目に書くことにしました。
(そう、私は12/24生まれです!{クリスマス,誕生日}{ケーキ,プレゼント}はいつも一つ。)

StreamでBuffer.concatしない冴えたやりかた

一般的にStreamを扱う時は on("data", ...) で処理することが多いと思います。
しかし、実際に使うサイズと流れてくるサイズは一致しないことが多く…

const net = require('net');

function onRecv(packet){ /* ... */ }

const server = net.createServer(conn => {
    let receivedQueue = Buffer.alloc(0);
    conn.on('data', chunk => {
        let data = Buffer.concat([receivedQueue, chunk]);
        //40バイトずつ読み出したい
        while(data.length >= 40){
            onRecv(data.slice(0, 40));
            data = data.slice(40);
        }
        receivedQueue = data;
    });
}).listen(3000);

のような処理になりがちです。…なんだか冗長に感じませんか?

他の言語では読み出し可能バイト数を取得、指定バイト以上なら一気に読み出す…みたいな処理を書くことが多いと思います。Arduinoのシリアル通信とか。
Node.jsで同じような処理は書けないのか?書けます。

Node.jsには、任意のバイト数を読み出すための readable イベントが存在します。

しかし、以前までは "読み出し可能なバイト数" を取得する方法が
stream._readableState.length
しかありませんでした。_が気になりますね…
でも今は大丈夫!
v9.4.0から、これを取得するための readableLength プロパティが追加されています。
stream.readableLength
罪悪感(?)が無くなりましたね。

これを使うと、先程のコードを以下のように書くことができます:

const net = require('net');

function onRecv(packet){ /* ... */ }

const server = net.createServer(conn => {
    conn.on('readable', () => {
        while(conn.readableLength >= 40){
            onRecv(conn.read(40));
        }
    });
}).listen(3000);

ああ…気持ちいい!これが書きたかった。これが求めていたもの・・・
コードが綺麗になることは勿論、Buffer.concatはデータの複製を行うので、処理速度の改善も期待できます。
このパターンが使える場面では積極的にreadableイベントを使っていきましょう。

注意点

この方法で読み込める最大バイト数は highWaterMark までになります。
大きなデータをまとめて取得したい時は、 highWaterMark の上限値を拡大する必要があります。
※ただ、デフォルトの highWaterMark を超えるデータを一気に処理する処理自体あまり良くないので、少しずつ処理できるようにしたほうがいいかもしれません。

p.s. highWaterMark は、シンプルに説明すると他言語のbufferSizeみたいなものです。(実際にはもっとイケてる実装の一部だったりしますが、とりあえずはそんなものだと思えば十分。)

さいごに

あまりreadableイベントを使った記事を見かけないので書いてみました。お試しあれ。
おわりー。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
5