console.lealog();

@leader22のWeb系に関する勉強めもブログですのだ

NodeJS製WebRTC DataChannel、NodeRTCのコードを読む Part.7

Part.1はこちら。

NodeJS製WebRTC DataChannel、NodeRTCのコードを読む Part.1 - console.lealog();

長かったこのシリーズもこれで最後です。

DataChannel

WebRTCのDataChannelは、

  • UDP
  • DTLS
  • SCTP

で構成されてる風ではあるけど実は最後にもう1層、DataChannel自体の層があるイメージになってる。

NodeRTCのコードを読み始めた段階では実装されてなかったこの最後の1ピースが、最近実装されたのでそこを読んでいく。

いわゆるEstablishment Protocolの部分。

nodertc/datachannel

読んだバージョンは`1.0.0`です。

GitHub - nodertc/datachannel: WebRTC Data Channel Establishment Protocol

ディレクトリはこのように。

.
├── channel.js
├── constants.js
├── handshake.js
├── index.js
└── protocol.js

ファイル数少なくて安心した。

使われ方

this.sctp.on('connection', socket => {
  console.log('[nodertc][sctp] got a new connection!');

  socket.on('stream', sctpStreamIn => {
    console.log('[nodertc][sctp] got stream %s', sctpStreamIn.stream_id);

    const sctpStreamOut = socket.createStream(sctpStreamIn.stream_id);

    const channel = createChannel({
      input: sctpStreamIn,
      output: sctpStreamOut,
      negotiated: true,
    });

    channel.once('open', () => {
      this.emit('channel', channel);
    });
  });

という感じ。

  • SCTPのコネクションで`socket`を得る
  • その`socket`の`on('stream')`で得たストリームを使ってChannelを作る
    • 前回みたSCTPの`createStream()`はココで使う
  • IN/OUTがあって、それぞれがSCTPのストリーム
  • `channel`のAPIで、WebRTCのクライアントにつながる実装がされてる
    • `write()`すれば送信できる
    • `on('data')`で受信できる

`createChannel()`は`index.js`に定義されてるけど、`new Channel()`するだけなのでそっちから。

class: Channel

  • extends `Duplex`
  • `constructor()`
    • `input` / `output`でそれぞれ`Readable` / `Writable`が必須
    • オプションはDCの挙動を制御するもの(`reliable`とか)
    • 実態は`HandshakeMachine`なるクラスが握ってる
    • `pipeline(input, handshake)`してる
    • `negotiated`のオプションを`true`にすると、先にHandshakeをはじめるために`opening()`を呼ぶ
  • `_read()`は空っぽ
  • `_write()`は`handshake`の状態に応じて、`output.write()`

今まで散々重い実装を読んできただけあって、すんなり理解できる。

class: HandshakeMachine

`negotiated`は`true`で初期化されるので、きっかけは`opening()`が呼ばれるところから。

  • `emit('postopen')`
  • 接続してるピア側に、`DATA_CHANNEL_OPEN`を送ってHandshakeを開始する
    • ピア側から返されるであろう`DATA_CHANNEL_ACK`を受け取るはず
  • ピア側(`input`)からのパケットは、`_transform()`で処理される
    • Handshakeが終わってないはずなので、`_handshake()`が呼ばれる
  • `_handshake()`
    • `emit('final')` -> `channel.emit('open')`

これでピア側とDataChannelがつながった。今度こそ。

読んでみて

実装が薄いからか今まで重いのを呼んでたからか、あっさり読めて良かった。

これくらいの薄さならRFCの全文を読んでもすんなりいけたので、まあそういうのの積み重ねなんかなーと思った。

そしてやっぱりTypeScriptでやらなかった理由は何なんやろう・・。

シリーズ通して読んでみて

そもそもDataChannelについてでいうと、やはりWebSocketで良いのでは感が拭えないけど、まあいろいろな経緯があっての今だと思うので、それはそれこれはこれで。

DataChannelでこれだけ大変だったので、MediaChannelとなるともっと大変なんやろなーWebRTC Implementorすげーなーというのが最終的な感想。

けど同時に、RFCさえ読めればやれんことはなさそうとも思えた。やるかどうかは別として。

個人的にはNodeJSでサーバー書くのもナシではないと思うので、引き続きリポジトリwatchしようと思います。