Part.1はこちら。
NodeJS製WebRTC DataChannel、NodeRTCのコードを読む Part.1 - console.lealog();
この記事では、ついにSCTPのレイヤーへ。
前回までのあらすじ
- `nodertc/nodertc`を読んでた
- クライアントとSessionを確立する際に、内部的にいくつかサーバーを立ててた
- STUN: 読んだ
- DTLS: 読んだ
- SCTP: 今回はコレ
というわけで、今回はSCTPの部分。
これも読んでみたら長くなったので、前後編になってます。
使われ方
startSCTP() { console.log('[nodertc][sctp] start'); this.sctp = sctp.createServer({ transport: this.dtls, }); this.sctp.once('listening', () => { console.log('[nodertc][sctp] server started'); }); 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); }); }); socket.on('error', err => { console.error('[nodertc][sctp]', err); }); }); this.sctp.on('error', err => { console.error('[nodertc][sctp]', err); }); this.sctp.listen(5000); // Port defined in SDP }
という感じ。
- SCTPの`createServer()`に、DTLSのStreamを渡す
- `on('connection')`で得られる`socket`の`on('stream')`から、DataChannelが作れる
- `listen()`で起動
サーバーとソケット、それぞれを見ていく必要がありそう。
nodertc/sctp
読んだバージョンは`0.1.0`です。
GitHub - nodertc/sctp: [WIP] SCTP network protocol in plain js
今回は`node_modules`って名前のディレクトリではないらしい。
lib ├── association.js ├── chunk.js ├── defs.js ├── endpoint.js ├── index.js ├── packet.js ├── reassembly.js ├── serial.js ├── sockets.js └── transport.js
さて、さっき見てたコードは`index.js`からエクスポートされてる。
というわけでまずは`createServer()`からですが、コレは`new Server()`してるだけなので、`Server`を。
class: Server
- extends `EventEmitter`
- `transport`のオプションは、`udpTransport`と名前を変えて保持される
`constructor()`ではほぼ何もしてない。
続いて`listen()`を見てみる。
- 実態は`_listen()`
- `Endpoint.INITIALIZE()`という大仰なやつを呼んでる
- `udpTransport`も渡される
- `on('association')`でサーバーが立って、`Socket`が返る
- `emit('connection', socket)`
`Endpoint`ってやつが何者なのかと、`Socket`を追う。
class: Endpoint
`INITIALIZE()`と`on('association')`を見る。
- `INITIALIZE()`
- `new Endpoint()`してる`static`メソッド
- `transport.register(endpoint)`
`Endpoint`本体はというと、
- extends `EventEmitter`
- `constructor()`では特に副作用はない
- `on('icmp')`と`on('packet')`くらい
- `this.udpTransport`も、このクラスでは使われてない
なので`transport.register(endpoint)`の先で何かやってるはず。
transport.register()
この関数だけを返してるファイル。
- `WeakMap`に`endpoint.udpTransport`を保持してる
- `endpoint.transport`に`UDPTransport`をセットして、それの`register()`を呼ぶ
コードを見るに、同じピアから複数回呼ばれることを想定してる。
class: UDPTransport / Transport
- extends `Transport`
- `udpTransport`を受け取ったあと、`on('data')`で`receivePacket()`
この`on('data')`がおそらくきっかけになって動き出す。
`register()`も`receivePacket()`も、`Transport`クラスに定義されてるやつ。
- `Transport`の`constructor()`
- `pool`に`port`ごとに`endpoint`を保持
- `register()`
- さっきの`pool`にポートを割り当てるだけ
- `endpoint.localPort`が埋められて返される
- `receivePacket()`
- `endpoint.emit('packet')`
さっきの`on('packet')`がココでつながる!
続 class: Endpoint
`on('packet')`で呼ばれるのは、`onPacket()`。
ただこいつが270行くらいある重い感じの処理・・。
ようは、
- 届いたパケットをそのまま流していいかチェックするのが仕事
- ついでにデコードして後で使おうとしてる
- ココで唐突に出てくる`association`という概念
- これがないなら`onInit()`
- あるなら`onCookieEcho()`
- もしくは`sendPacket()`
- 送られてきたパケット、`Chunk`によって処理が変わるっぽい
Associationとはなんぞや?というと、SCTPのエンドポイントをペアにして扱う単位みたいなもので、各エンドポイント = ノードは単数かもしれないし複数かもしれない・・みたいなことがRFCに書いてあった。
`Association`のクラスは、2000行くらいあって読めたもんじゃなかったので割愛。
ちなみにさっきの`onPacket()`のくだりで、`onCookieEcho()`にたどり着いた場合、最初のほうで待ってた`on('association')`が発火するようになってる。
`association.acceptRemote()`で内部的な状態を初期化してる。
これでやっとSCTPのSocketが手に入る・・。