webブラウザでp2pを実現する、webrtcのapiと周辺技術
DESCRIPTION
TRANSCRIPT
WebブラウザでP2Pを実現する、WebRTCのAPIと周辺技術
2014/04/19
IntroYoshiaki Sugimoto
CyberAgent.inc
Developer
JavaScript / Node.js / CreateJS
PHP / Linux
WebRTCの概要
WebRTCのJavaScript実装
P2Pの仕組みとNAT Traversal
STUN / TURN
Appendix
Agenda
トランスポート層の話
TCP / UDP その他プロトコル
NAT関連
Attention
WebRTCの概要
Web Real-Time Communicationリアルタイムコミュニケーションを行うためのAPI実装
ビデオチャット・ファイル共有 ( ≒ )
周辺技術はオンラインゲームにも
Description
WebSocketサーバを介して(TCP)データを送受信
WebRTC端末同士が直接(UDP)データを送受信
WebSocket?
WebSockettransport
WebSocket Server
Local Machine Local Machine
TCP TCP
broadcastunicast
WebRTC(P2P)transport
UDP / unicast
Local Machine Local Machine
SignalingServer
STUN TURNServer
データの整合性や順序・再送や結果の保証(タイムアウトなども含む)を考慮すべき通信 -> WebSocket
オーバーヘッドが少なく、多少パケットロスがあっても問題のないシンプル且つ高速なデータ通信
-> WebRTC
WebSocketvs
WebRTC
WebRTCServices
https://www.sharedrop.io
https://vline.com
JavaScript API
RTCPeerConnection
RTCSessionDescription
RTCIceCandidate
navigator.getUserMedia
JavaScriptAPI
WebRTC関連のAPIは合計3つ
Media Stream関連のAPIから1つ
RTCPeerConnection
Peer(ピア)を生成するメインAPI
このオブジェクトを介してP2P通信を行う
いくつかのメソッド・イベントインターフェース
webkitRTCPeerConnection
mozRTCPeerConnection
JavaScriptAPI
RTCSessionDescription
Session Description Protocolに関するデータオブジェクト
このデータを双方で共有することでP2P接続が確立される
RTCSessionDescription
mozRTCSessionDescription
JavaScriptAPI
JavaScriptAPI
RTCIceCandidate
ICEによる接続経路候補(Candidate)に関するオブジェクト
host / srflx / relay など
RTCIceCandidate
mozRTCIceCandidate
JavaScriptAPI
navigator.getUserMedia
ブラウザからMedia Streamに関する接続を可能にするAPI
現在はカメラ・マイクデバイスの入力のみサポート
navigator.webkitGetUserMedia
navigator.mozGetUserMedia
JavaScriptLibrary
PEERJSベンダープレフィックスやブラウザの実装差異を吸収しつつ、最適な方法で通信を行うようサポートしているライブラリ
イベントベースの扱いやすいインターフェース
SkyWay ( http://nttcom.github.io/skyway/ )はこれをforkしたライブラリを使用している
P2P接続のフローとその実装- ビデオチャット -
Demonstration
https://github.com/ysugimoto/RTCPeerConnectionSample
P2PConnection
P2Pの接続確立フロー
Peer(ピア)の生成
データ・ビデオストリームの接続
Session Description Protocolの交換
Offer / Answer
Signaling
P2P通信経路候補の共有(IceCandidate)
データ送信の共有 or ビデオストリームの共有
var peer = new webkitRTCPeerConnection({ "iceServers": [{"url": "stun:stun.l.google.com:19302"}]});
Peerの生成
“iceServers”というプロパティを持つオブジェクトを引数に渡す
var websocket = new WebSocket(‘ws://www.xxx.yyy.zzz’);
Signalling用にWebSocketの接続も行う
メディア接続// メディアに関する設定
var constraint = { audio: true, video: true};
navigator.webkitGetUserMedia( constraint, successCallback, errorCallback);
constraint: audio: マイクを使うかどうか video: カメラを使うかどうか
successCallback: メディア接続成功時のコールバック errorCallback: メディア接続失敗時のコールバック
自分のストリーム接続
// 自分のストリームはMediaStream APIから取得するnavigator.webkitGetUserMedia( { audio: true, video: true }, function(stream) { // video要素取得 var video = document.getElementById('localVideo');
// srcにBlob URLを指定するとカメラの画像がストリームで流れる video.src = window.webkitURL.createObjectURL(stream);
// 自分のpeerにカメラストリームを接続させる peer.addStream(stream); }, function(err) { console.log(err.name + ': ' + err.message); });
相手のストリーム接続
// 相手のストリームはP2Pのイベントから取得するpeer.onaddstream = function(stream) { // 自分のリモートにセット var video = document.getElementById('remoteVideo');
video.src = window.webkitURL.createObjectURL(stream);};
onaddstreamイベントハンドラで接続
Offer / Answer OfferAnswer
Session Description Protocolの生成
Local / RemoteのSDPをセットする
電話での通話イメージに近い
Offer / AnswerTelephone
Bさんに電話しよう Aさんから着信だ
電話に出ようつながった
Phone Carrier Server
Offer送信Local Descriptionセット Offer着信
Remote Description着信
Answer送信Remote DescriptionセットLocal Descriptionセット
Answer着信Remote Descriptionセット
① ②③④
SignalingP2P
Bさんと通信しよう Aさんからcallだ
応答しようつながった
Signaling Server(WebSocket)
Offer送信Local Descriptionセット Offer着信
Remote Description着信
Answer送信Remote DescriptionセットLocal Descriptionセット
Answer着信Remote Descriptionセット
① ②③④
Offerの送信(発信者)// Offer送信peer.createOffer(function(sdp) { // 引数のSDPは自分用 peer.setLocalDescription(sdp, function() { // セット完了したら、相手に自分のSDPを送る(signaling) websokcet.send(JSON.stringify({ "sdp": sdp, "uuid": uuid })); });});
SignalingOffer
Bさんと通信しよう Aさんからcallだ
応答しようつながった
Signaling Server(WebSocket)
Offer送信Local Descriptionセット Offer着信
Remote Description着信
Answer送信Remote DescriptionセットLocal Descriptionセット
Answer着信Remote Descriptionセット
① ②③④
Offerの受け取りとAnswerの生成(応答者)// websocketのメッセージイベントで受け取るwebsocket.onmessage = function(evt) { var message = JSON.parse(evt.data), sdp;
if ( message.sdp ) { sdp = new RTCSessionDescription(message.sdp); // 相手用(remote)にセット peer.setRemoteDescription(sdp, function() { // 自分へのOffer-SDPだったらAnswerを返す if ( sdp.type === "offer" ) { peer.createAnswer(function(sdp) { peer.setLocalDescription(sdp, function() {
// セット完了したら、相手に自分のSDPを送る websokcet.send(JSON.stringify({
"sdp": sdp, "uuid": uuid }));}));
}); } }); }};
------ ②・④------------ ③
SignalingSignaling
Bさんと通信しよう Aさんからcallだ
応答しようつながった
Signaling Server(WebSocket)
Offer送信Local Descriptionセット Offer着信
Remote Description着信
Answer送信Remote DescriptionセットLocal Descriptionセット
Answer着信Remote Descriptionセット
① ②③④
Signaling Servervar ws = require(‘websocket.io’);var server = ws.listen(8124);
// 接続イベントserver.on(‘connection’, function(socket) { socket.on(‘message’, function(data) { console.log(‘Message received:’ + data); // 接続者全員にブロードキャスト server.client.forEach(function(client) { client && client.send(data); }); });});
シンプルなWebSocket
$ node server.js
SDPSession Description Protocol(SDP)
クライアントPeerの接続情報に関するテキストデータ
送信元IP、Mediaはこれを使う、Audioをこれを使う、など
シグナリングで交換する
自分のSDPをLocal Description、相手のSDPをRemote Descriptionと呼ぶ
SDPSDP Sample
v=0o=- 6636874602225569115 2 IN IP4 127.0.0.1...m=audio 1 RTP/SAVPF 111 103 104 0 8 106 105 13 126...m=video 1 RTP/SAVPF 100 116 117...a=sctpmap:5000 webrtc-datachannel 1024
a=ssrc:3712708814 cname:dgbXDOpXGNofSBNBa=ssrc:3712708814 msid:RTCDataConnection RTCDataConnectiona=ssrc:3712708814 mslabel:RTCDataConnectiona=ssrc:3712708814 label:RTCDataConnection
通信経路の共有(発信者/応答者)// STUNサーバから経路候補が見つかるたび発火peer.onicecandidate = function(evt) { var candidate;
// evt.candidateプロパティにデータが入っているので、WebSocketで送信 if ( evt.candiate ) { websocket.send(JSON.stringify({"candidate": evt.candidate})); }};
// websocketのメッセージハンドラ内で送信されてきたデータを復元してセットするwebsocket.onmessage = function(evt) { var message = JSON.parse(evt.data), candidate;
// evt.candidateがあればCandidateの共有 if ( evt.candidate ) { candidate = new RTCIceCandidate(evt.candidate); peer.addIceCandidate(candidate); }};
IceCandidateIceCandidate
STUN(TURN)サーバにリクエストを送り、自分のNAT環境から通信可能な経路候補(Candidate)を取得する
候補が見つかるとPeerに通知されるので、これをSignalingと同様に相手と共有する
経路は複数見つかる(使用できるかどうかは別)
IceCandidateSTUN Server TURN Server
C1 C1
C1: 同一 NAT内(host)
IceCandidateSTUN Server TURN Server
C1 C1
C2 C2 C1: 同一 NAT内(host)
C2: STUNによるNAT Traversal(srflx)
IceCandidateSTUN Server TURN Server
C1: 同一 NAT内(host)
C1 C1
C2 C2C2: STUNによるNAT Traversal(srflx)
C3 C3
C3: TURNによるNAT Traversal(relay)
IceCandidate sampleIceCandidate
mid: video, candidate: a=candidate:1574647786 1 \ udp 2113937151 xxx.xxx.xxx.xx 64219 \ typ host generation 0
mid: video, candidate: a=candidate:4102338623 1 \ udp 1845501695 yyy.yyy.yyy.yy 41073 \ typ srflx raddr xxx.xxx.xxx.xx rport 64219 generation 0
Debug Tool
chrome://webrtc-internalsPeerセッションのイベントや
全てのコネクションデータが確認可能
P2Pの仕組みとNAT Traversal
クライアントが相互に通信手段を共有-> SDPの交換(Signaling)
通信経路候補の共有 (Candidate)
端末(Peer)同士が直接パケットを送受信する (Connected)
P2PMechanism
Local MachineG: 123.456.789.00
Local MachineG: 009.876.543.21
SignalingServer
STUN TURNServer
P2PMechanism
Signaling
SDP SDP
SignalingServer
STUN TURNServer
P2PMechanism
Candidate
Candidate
Local MachineG: 123.456.789.00
Local MachineG: 009.876.543.21
UDP / unicast
SignalingServer
STUN TURNServer
P2PMechanism
Connected
Local MachineG: 123.456.789.00
Local MachineG: 009.876.543.21
P2PMechanism
通常は経路にNATを挟む
P2Pは送受信先の完全なアドレスとポートを知る必要がある
NATによって変換される前のプライベートアドレスに届けるには、NATを越える必要がある-> NAT Traversal
※正確には、相手端末にパケットが届くようなNATのAddress:Portを得る
SignalingServer
STUN TURNServer
P2PMechanism
Connected
Local MachineP: 192.168.0.10
Local MachineP: 192.168.0.1
G: 111.222.333.44G: 555.666.777.88
UDP / unicast
NAT TraversalNATによって見えない相手の端末に
直接パケットを届けるための技術
P2Pを用いるオンラインゲームではすでに導入や研究が進んでいる-> WebRTCにおいても仕組みは同じ
NATの特性によっては難しい場合もある
Full cone NATNATの種類
どのIP:Portからでもパケットを受信するNAT
Local MachineP: 192.168.0.1:255
G: 111.222.333.44:80
G:xxx.xxx.xxx.xx
G:yyy.yyy.yyy.yy
packet
Host A
Host B
Address-Restricted NATNATの種類LocalMachineがパケットを送ったことがある
アドレスからのパケットを受け取る
Local MachineP: 192.168.0.1:255
G: 111.222.333.44:80
G:xxx.xxx.xxx.xx
G:yyy.yyy.yyy.yy
packet
Host A
Host B
Port-Restricted NATNATの種類Address-Restrictedに加え、送信元のポートも
一致した場合のみ受信する
Local MachineP: 192.168.0.1:255
G: 111.222.333.44:80
G:xxx.xxx.xxx.xx:60313
G:yyy.yyy.yyy.yy
packet
Host A
Host B
Symmetric NATNATの種類内部パケットは全て唯一のIP:Portにマップ
されるNAT
Local MachineP: 192.168.0.1:255
G: 111.222.333.44:80
G:xxx.xxx.xxx.xx:60313
G:yyy.yyy.yyy.yy
packet
Host A
Host B
内部からのパケットを受け取った外部ホストからのみのパケットを受信する
Universal Plug and Play
デバイスを接続するだけでネットワークに参加できる仕組み
動的にNAT変換テーブルを制御可能
モデム側で対応していないといけない
UPnP
NATタイプによるP2P
NAT Type Full cone Address-Rest Port-Rest Symmetric
Full cone
Address-Rest
Port-Rest
Symmetric
P2PEnables
STUN / TURN
Simple Traversal of UDP through NATs
RFC3489にて仕様策定
stun.l.google.com:19302
stun.services.mozilla.com
STUN
STUNの役割と動作クライアントがセッションを開始する際に、STUNサーバへリクエストを送信し、NATの情報を取得する
STUNは実装されているアルゴリズムでNATタイプを調査し、有効なNATアドレスを返却する
STUN
STUNAlgorithm
異なるIP:Portからのecho要求
Full cone NAT
受け取れた 受け取れなかった
同じIP:Portからのecho要求(サーバ#2)
PublicIP: xxx.xxx.xxx.xx:yy
PublicIP変わってないPublicIP変わった
Symmetric NAT同じIP:異なるPortからのecho要求
受け取れた 受け取れなかった
Address-Restricted NAT Port-Restricted NAT: OK: NG
var PublicIP = ‘xxx.xxx.xxx.xxx’;
if ( Stun.hasReceivedFrom(defferentAddr, differentPort) ) { return Stun.FullConeNATs;} else if ( Stun.hasReceivedFrom(sameAddr, samePort) ) { if ( Stun.isConstantIP(PublicIP) ) { return Stun.SymmetricNATs; }
if ( Stun.hasReceivedFrom(sameAddr, differrentPort) ) { return Stun.AddressRestrictedNATs; } else { return Stun.PortRestrictedNATs; }}
STUNAlgorithm
UDP Hole PunchingSTUN
NATの内側から特定IP:Portにパケットを送信させ、NAT側に送信元IP:Portで受信可能なマッピングを生成させる手法。
双方のクライアントはSTUNサーバから得られた互いの送信元IP:Port同士にパケットを送り合い、受信可能な状態にする。
主にRestricted-NATに対してP2P接続が有効になる
STUNUDP Hole Punching
Restricted-NATの特性を利用し、STUNサーバ(外部NAT)に向けてパケットを送信したIP:Portを伝える
Local Machine AP: 192.168.0.1:255
xxx.xxx.xxx.xx:3478
STUN Server
①:パケット送信
②:この送信元IP:PortをLocal Machineに伝える
NAT-Bも同様に
IP:Port A
IP:Port B
NAT-A
STUNUDP Hole Punching(1/3)
Local Machine AP: 192.168.0.1:255
Local Machine BP: 192.168.0.2:255
NAT-ANAT-B
IP:Port AIP:Port B
IP:PortA->IP:PortBへパケットを送信するが、NAT-Bはこのパケットを破棄する
IP:PortB -> IP:PortAへのパケットは
疎通可能になる(NAT-A)
STUNUDP Hole Punching(2/3)
Local Machine AP: 192.168.0.1:255
Local Machine BP: 192.168.0.2:255
NAT-ANAT-B
IP:Port AIP:Port B
IP:PortB->IP:PortAへパケットを送信すると、NAT-Aはこのパケットを受信する
IP:PortA -> IP:PortBへのパケットは
疎通可能になる(NAT-B)
STUNUDP Hole Punching(3/3)
Local Machine AP: 192.168.0.1:255
Local Machine BP: 192.168.0.2:255
NAT-ANAT-B
IP:Port AIP:Port B
IP:PortA->IP:PortBへ再度パケットを送信すると、NAT-Bは今度はパケットを受信する
IP:PortA / IP:PortBにUDPパケットの穴が開いた状態
NATタイプによるP2P
NAT Type Full cone Address-Rest Port-Rest Symmetric
Full cone
Address-Rest
Port-Rest
Symmetric
P2PEnables
Using UDP Hole Punching
Traversal Using Relay NAT
パケット送受信を外部サーバがRelay
することで、より完全なNAT越え問題を解決する
その特性より、主にSymmetric NATに対して有効であるプロトコル
RFC 5766にて仕様策定
TURN
TURNの役割と動作“内部からのパケットを受け取った外部ホストからのみのパケットを受信する” というSymmetric NATの特性を解決できる(TURNサーバがRelayしてパケットを投げるため)
パケットのRelay機構が外部に存在するため、TURNサーバには大きな負荷がかかり、サーバダウンによる障害も
ネットワーク経路としてはWebSocketに類似(UDP/TCP変換もサポート)
TURN
TURN Relay Server
Local MachineP: 192.168.0.1:255
xxx.xxx.xxx.xx:34567 TURNTURN Server
Local MachineP: 192.168.0.2:255
Relay
NATタイプによるP2P
NAT Type Full cone Address-Rest Port-Rest Symmetric
Full cone
Address-Rest
Port-Rest
Symmetric
P2PEnables
Using UDP Hole PunchingUsing TURN Relay
Interactive Connectivity Establishment
STUN / TURNを含め、NAT Traversalへの最適な手法を提供する規格
ICE
Appendix- リアルタイム通信対戦-
Peer同士で任意のデータの送受信が可能
String / Blob / ArrayBuffer
SCTP - Reliable Mode
RTP - Non Reliable Mode
Data Channel API
older
DataChannel生成(1/2)
// SDPを生成する前に作成しておく必要があるvar dataChannel = peer.createDataChannel('RTCDataChannel');
// イベントなどを初期化initializeDataChannel(dataChannel);
// 相手からのDataChannelの接続はイベントで監視peer.ondatachannel = function(evt) { // evt.channelにDataChannelが格納されている dataChannel = evt.channel; initializeDataChannel(dataChannel);};
// データ送信はsend()メソッドdataChannel.send('some data');
function initializeDataChannel(dataChannel) { dataChannel.onmessage = function(evt) { var message = evt.data;
// do something };
dataChannel.onopen = function() { // do something };
dataChannel.onclose = function() { // do something };
dataChannel.onerror = function() { // do something };}
DataChannel生成(2/2)
WebSocketと同じイベントI/F
https://github.com/ysugimoto/WebRTetris
Demonstration
Data Channel TransportsReliable modeSCTPプロトコルで転送データの順序は保証され、再送制御も内部でかかる
Non-Reliable modeRTPプロトコルで転送データの順序は保証されず、再送制御も実装する必要がある
Channel間で互換性が保てない><
older
UDPの仕様上、IPv4では64KB/send の制限
チャンク化の送信 / 受信は実装しないといけない
間のデータ転送は16300Byte/send
に制限されてしまう ( via PeerJS )
Some Problems...
conclusion
先行実装ブラウザで大体の機能が使える
特にData Channel APIが動くようになった
DTMF Sender APIはまだ実装が怪しい
リアルタイム通信がWebSocketと2極化するかも
使い分けが大事
Present
プラグインレスで稼働するP2Pオンラインゲーム
サーバ負荷の低い大規模データ配信
いずれはスマートフォンでも利用可能に?
現在はAndroidのChrome29+のみ
Future
WebRTC - Overviewhttp://www.webrtc.org/
[PDF] ICE TURN/STUN tutorialhttp://sdstrowes.co.uk/talks/20081031-ice-turn-stun-tutorial.pdf
WebRTC 1.0: Real-time Communication Between Browsershttp://www.w3.org/TR/webrtc/
WebRTC in the real world: STUN, TURN and signalinghttp://www.html5rocks.com/en/tutorials/webrtc/infrastructure/
Interactive Connectivity Establishment (ICE):A Protocol for Network Address Translator (NAT) Traversal for Offer/Answer Protocolshttps://tools.ietf.org/html/rfc5245
SDP: Session Description Protocolhttp://tools.ietf.org/html/rfc4566
STUN - Simple Traversal of User Datagram Protocol (UDP)Through Network Address Translators (NATshttp://tools.ietf.org/html/rfc3489
Traversal Using Relays around NAT (TURN):Relay Extensions to Session Traversal Utilities for NAT (STUN)http://tools.ietf.org/html/rfc5766
Thanks!Resources