GitHub - nodertc/nodertc: [WIP] WebRTC Datachannels for Node.js
JavaScriptで書かれたWebRTCの実装で、現時点ではDataChannelのみ実装されてます。
WebRTCスタックの実装、興味はあって前々から読んでみたいとは思ってたものの、RFCの数も多いし高い壁よね・・。
というところで、DCだけやしコードもJSやし、これならなんとかなるんでは?という。
QUICがきても・・この経験は・・きっと無駄にはならな・・。
必要なRFCをうまくたどって読むのが大変そうなので、実装を先に読めばそのへんの雰囲気がつかめるのでは?という主旨のシリーズです。
ちなみに読んだバージョンは、`0.1.0`です。
まず試してみる
サンプルが用意されてるのでそれで。
- `git clone`する
- `npm i`する
- `npm start`する
- `localhost:7007`をひらく
- DevToolsから、`channel.send('hello')`とかしてみる
- `npm start`したコンソールになんか出るのを確認する
- コンソールになんか入力してみる
- ブラウザ側からなんか出るの確認する
ブラウザとサーバー間でP2Pしてるだけなのでコードもシンプル。
20181128追記:
コードを読み始めた時は送受信の機能はなかったけど、ついに実装されました!
サーバー側
`example-express.js`より。
`express`のサーバーのサンプルなので、使い方は見ればわかるはずやけど一応。
const nodertc = require('nodertc'); // サーバーを立てて待機 const rtc = nodertc(); await rtc.start(); // offerをもらったら新規セッションを作る const session = rtc.createSession(); // このanswerをクライアントに送り返す const answer = await session.createAnswer(offer); rtc.on('session', session => { session.once('channel', channel => { // 送信 channel.write(line); // 受信 channel.on('data', data => { console.log(`${data.toString()}`); }); }); });
今のところはVanilla ICE専用っぽい。
nodertc/nodertc
さて本題。
まずは本丸であるこのリポジトリから。
- index.js
- lib/candidates.js
- lib/fingerprint.js
- lib/grammar.js
- lib/ice-util.js
- lib/sdp.js
Organizationとしての`nodertc`配下にもたくさんのRepoがあって内部的に依存してる。
必要があればその`node_modules`も読む。
lib/
小さな関数がいくつかあるだけ。
`grammar.js`は、`ice-util.js`のために存在する。ICEのユーザー名とかパスワードとかの文字列を生成するやつ。
index.js
大きく分けて2ついる。
- NodeRTC
- Session
こいつらは後述するとして、このパッケージとしては、`new NodeRTC(options)`する関数を返してるだけ。
class: NodeRTC
100行弱。
主要なのはこの2つで、あとはGetterがついてるだけ。
- start(options)
- createSession()
つくったSessionを格納したり、オプションで`certificate`と`certificatePrivateKey`を受け取って、あとでSDPに載せる時に使ってる。
start(options)
このサーバーのIPを決めてるだけ。
- GitHub - sindresorhus/public-ip: Get your public IP address - very fast!
- GitHub - sindresorhus/internal-ip: Get your internal IP address
ここにも卿がいらっしゃった・・。
class: Session
300行ちょい。
`createAnswer()`のほかにもいくつかメソッドがあるけど、全ての起点はコレ。
あとは`constructor()`でUDPのソケットを用意したり、ICEのユーザー名・パスワードを用意したり。
createAnswer(offer)
- `offer`のSDPをパース
- `appendCandidate()`
- 受け取った`candidate`を保持しつつ
- `unicast`のソケットを用意
- `unicast`のソケットのHostとPortは、`priority`が一番高い`candidate`の
- `listen()`で各種サーバーを立てる
- STUNサーバー
- `constructor()`で用意してたUDPのソケットを使って
- GitHub - nodertc/stun: Low-level Session Traversal Utilities for NAT (STUN) server
- DTLSサーバー
- `unicast`のソケットを使って
- GitHub - nodertc/dtls: Secure UDP communications using DTLS.
- 最後にSCTPサーバー
- さっきのDTLSをトランスポートに指定
- GitHub - nodertc/sctp: [WIP] SCTP network protocol in plain js
- ポートは`5000`固定になってる
- `answer`のSDPを作ってクライアントに返す
これを接続してきたピアごとにやってる。
接続してきたピアごとに用意した`unicast`のUDPソケットが全ての基盤で、その上でDTLS、SCTPという構造。
unicastとは
GitHub - reklatsmasters/unicast: Unicast implementation of UDP Datagram sockets.
`dgram.createSocket()`のラッパー。
まさにその名の通りで、最初に指定したHostとPortに合致した`message`だけ通すようになってる。
const unicast = require('unicast'); const socket = unicast.createSocket({ type: 'udp4', port: 2222, remotePort: 1111, remoteAddress: '127.0.0.1' });
これで得られる`socket`は、`Duplex`のストリーム。
ラップする前は`Duplex`じゃない。
読んでみて
- コードは割ときれいで読みやすい
- ただ`Symbol`で`private`っぽいコード書くならTypeScriptとかにしてほしい
- コードはわかりやすいが、なぜそうするのかを完全には理解できない
- こういうところが結局RFC読め案件なんやろな・・
STUN, DTLS, SCTPの実装のほうが本体っぽい予感がしてるので、次回以降はそっちを読んでく。