LoginSignup
7
5

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-12-23

はじめに

この記事は 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イベントを使った記事を見かけないので書いてみました。お試しあれ。
おわりー。

7
5
0

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
  3. You can use dark theme
What you can do with signing up
7
5