11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

はじめてのWebRTC

Last updated at Posted at 2023-09-13

WebRTCって何?

「Web Real-Time Communication」

Webブラウザ同士でリアルタイムにデータのやり取りをする仕組みのこと。
大容量の映像や音声データをリアルタイムに送受信できる。
WebRTC は P2P を利用したブラウザ同士を前提とした技術として作られている。

現在は、デスクトップとモバイルの「Google Chrome」「Safari」「Firefox」「Opera」等ほぼすべてのブラウザで利用できるようになっており、余計なプラグインのインストールは不要。ブラウザさえあれば使用可能。

WebRTCは通信をP2Pで行うため、サーバーを介さずリアルタイムのやり取りが可能。P2Pが使われるサービスは、たとえば「LINE」「Spotify」「BitCoin」「Ethereum」など。

※上記のサービス例は、WebRTCは使用されていないが、通信はP2P

WebRTCでできることの例

・リアルタイムなコミニュケーションの為のデバイス(マイク・カメラ)管理・スクリーンキャプチャ
・デバイス認識・選択
・デバイスから取得した大量の映像や音声のデータの圧縮と送信(コーデック)
・P2Pで相手と通信
など・・・

WebRTC は沢山の技術の集合体と考えてもらって問題ない。 それらをブラウザから簡単に使えるようにした仕組みが WebRTC API である。

WebRTC にはメディアチャネルとデータチャネルの二つがある。

  1. 映像と音声のデータ通信方式がメディアチャネル
  2. 好きなデータをやりとりできるのがデータチャネル

シグナリングサーバーとは

「通信相手に関する情報を得る」のが役割のサーバー

STUNサーバーとは

外部ネットワークから見た際の自身のIPアドレスを教えてくれます。そのアドレスと自身のPCのアドレスを比較してNAT越えが必要かを判断します。

NATとは

ネットワークアドレスを変換する機能のこと。
例えば、自宅でWi-Fiをつないだ際、端末にグローバルIPアドレスが付与されていないケースがあります。これは、プロバイダーから割り振られるIPアドレスが一つのため、複数の端末がインターネットに接続できるよう、ルーターがIPをLANの端末へ振り分けているためです。
そのため、端末で表示されるIPアドレスがグローバルIPアドレスと異なることがあります。しかし、これでは情報を送りたい側は困ってしまいます。なぜなら、通信したい先の本当のIPアドレスが変換されているため、わからないからです。
そこで、NATによって変換されたアドレスをセキュアに関連づける「NAT超え」が必要となります。その際に必要となるのが、STUNサーバーとTURNサーバーです。

TURNサーバーとは

STUNサーバーを使えば、NAT超えが必要かどうかがわかります。実際に企業で導入しているネットワークにはNAT越えが必要な場合が多く、且つ、Firewallを超える必要もでてきます。

Firewallを超える

Firewallは、ほとんどのパソコンに組み込まれた基本的なセキュリティ対策の1つです。特に、企業においては、サイバー攻撃などのリスクを防ぐため、ポート制御やウイルス感染を防ぐ役割を担います。
しかし、Firewallをセキュアに超えられる仕組みがなければ、ビジネスシーンでP2Pのような通信ができません。そこで、考えられたのが「TURNサーバー」です。

引用

下記はデータチャネルのデモソース
深掘れば深掘るほど、奥が深く、理解がしづらいですが・・・
P2P通信でリアルタイムチャットを実際に動かしてみることで、P2Pについての理解が深まった気がします・・・
なにより、HTMLとJavascriptだけでP2Pのリアルタイム通信ができるってすごい!

HTML


<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>WebRTC Demo</title>
</head>
<body>
    <h1>WebRTC Data Channel Demo</h1>

    <h2>シグナリング</h2>
    <p>状態: <input type="text" id="status" value="" readonly="readonly" /></p>

    <h3>SDPの生成</h3>
    <p>(手順1) ブラウザ1で Start を押し,SDP (offer) を生成する。</p>
    <button type="button" onclick="startPeerConnection()">Start</button>
    <h3>自端末のSDP (Read-only)</h3>
    <p>(手順2) ブラウザ1からこのSDP (offer) をコピーする。</p>
    <p>(手順4) ブラウザ2で生成したこのSDP (answer) をコピーする。</p>
    <textarea id="localSDP" cols="80" rows="5" readonly="readonly"></textarea>
    <h3>他端末のSDP (手動でセットする)</h3>
    <p>(手順3) ブラウザ2で,コピーしたブラウザ1のSDP (offer) を貼り付け Set を押すと,自端末のSDPに返答用 SDP (answer) が生成される。</p>
    <p>(手順5) ブラウザ1で,コピーしたブラウザ2のSDP (answer) を貼り付け Set を押す。</p>
    <textarea id="remoteSDP" cols="80" rows="5"></textarea>
    <button type="button" onclick="setRemoteSdp();">Set</button>

    <h2>データチャネルでの通信</h2>
    <form action="javascript:sendMessage()">
        <input type="text" id="message" size="30" value="" />
        <input type="submit" value="Send" />
    </form>
    <textarea id="history" cols="80" rows="10" readonly="readonly"></textarea>

</body>
</html>

javascript


// ICE server URLs
let peerConnectionConfig = {'iceServers': [{"urls": "stun:stun.l.google.com:19302"}]};

// Data channel オプション
let dataChannelOptions = {
    ordered: false,
}

// Peer Connection
let peerConnection;

// Data Channel
let dataChannel;

// ページ読み込み時に呼び出す関数
window.onload = function() {
    document.getElementById('status').value = 'closed';
}

// 新しい RTCPeerConnection を作成する
function createPeerConnection() {
    let pc = new RTCPeerConnection(peerConnectionConfig);

    // ICE candidate 取得時のイベントハンドラを登録
    pc.onicecandidate = function(evt) {
        if ( evt.candidate ) {
            // 一部の ICE candidate を取得
            // Trickle ICE では ICE candidate を相手に通知する
            console.log(evt.candidate);
            document.getElementById('status').value = 'Collecting ICE candidates';
        } else {
            // 全ての ICE candidate の取得完了(空の ICE candidate イベント)
            // Vanilla ICE では,全てのICE candidate を含んだ SDP を相手に通知する
            // (SDP は pc.localDescription.sdp で取得できる)
            // 今回は手動でシグナリングするため textarea に SDP を表示する
            document.getElementById('localSDP').value = pc.localDescription.sdp;
            document.getElementById('status').value = 'Vanilla ICE ready';
        }
    };

    pc.onconnectionstatechange = function(evt) {
        switch(pc.connectionState) {
        case "connected":
            document.getElementById('status').value = 'connected';
            break;
        case "disconnected":
        case "failed":
            document.getElementById('status').value = 'disconnected';
            break;
        case "closed":
            document.getElementById('status').value = 'closed';
            break;
        }
    };

    pc.ondatachannel = function(evt) {
        console.log('Data channel created:', evt);
        setupDataChannel(evt.channel);
        dataChannel = evt.channel;
    };

    return pc;
}

// ピアの接続を開始する
function startPeerConnection() {
    // 新しい RTCPeerConnection を作成する
    peerConnection = createPeerConnection();

    // Data channel を生成
    dataChannel = peerConnection.createDataChannel('test-data-channel', dataChannelOptions);
    setupDataChannel(dataChannel);

    // Offer を生成する
    peerConnection.createOffer().then(function(sessionDescription) {
        console.log('createOffer() succeeded.');
        return peerConnection.setLocalDescription(sessionDescription);
    }).then(function() {
        // setLocalDescription() が成功した場合
        // Trickle ICE ではここで SDP を相手に通知する
        // Vanilla ICE では ICE candidate が揃うのを待つ
        console.log('setLocalDescription() succeeded.');
    }).catch(function(err) {
        console.error('setLocalDescription() failed.', err);
    });

    document.getElementById('status').value = 'offer created';
}

// Data channel のイベントハンドラを定義する
function setupDataChannel(dc) {
    dc.onerror = function(error) {
        console.log('Data channel error:', error);
    };
    dc.onmessage = function(evt) {
        console.log('Data channel message:', evt.data);
        let msg = evt.data;
        document.getElementById('history').value = 'other> ' + msg + '\n' + document.getElementById('history').value;
    };
    dc.onopen = function(evt) {
        console.log('Data channel opened:', evt);
    };
    dc.onclose = function() {
        console.log('Data channel closed.');
    };
}

// 相手の SDP 通知を受ける
function setRemoteSdp() {
    let sdptext = document.getElementById('remoteSDP').value;

    if ( peerConnection ) {
        // Peer Connection が生成済みの場合,SDP を Answer と見なす
        let answer = new RTCSessionDescription({
            type: 'answer',
            sdp: sdptext,
        });
        peerConnection.setRemoteDescription(answer).then(function() {
            console.log('setRemoteDescription() succeeded.');
        }).catch(function(err) {
            console.error('setRemoteDescription() failed.', err);
        });
    } else {
        // Peer Connection が未生成の場合,SDP を Offer と見なす
        let offer = new RTCSessionDescription({
            type: 'offer',
            sdp: sdptext,
        });
        // Peer Connection を生成
        peerConnection = createPeerConnection();
        peerConnection.setRemoteDescription(offer).then(function() {
            console.log('setRemoteDescription() succeeded.');
        }).catch(function(err) {
            console.error('setRemoteDescription() failed.', err);
        });
        // Answer を生成
        peerConnection.createAnswer().then(function(sessionDescription) {
            console.log('createAnswer() succeeded.');
            return peerConnection.setLocalDescription(sessionDescription);
        }).then(function() {
            // setLocalDescription() が成功した場合
            // Trickle ICE ではここで SDP を相手に通知する
            // Vanilla ICE では ICE candidate が揃うのを待つ
            console.log('setLocalDescription() succeeded.');
        }).catch(function(err) {
            console.error('setLocalDescription() failed.', err);
        });
        document.getElementById('status').value = 'answer created';
    }
}

// チャットメッセージの送信
function sendMessage() {
    if ( !peerConnection || peerConnection.connectionState != 'connected' ) {
        alert('PeerConnection is not established.');
        return false;
    }
    let msg = document.getElementById('message').value;
    document.getElementById('message').value = '';

    document.getElementById('history').value = 'me> ' + msg + '\n' + document.getElementById('history').value;
    dataChannel.send(msg);

    return true;
}

下記がサンプルデモも有り分かりやすかったです。
https://webrtc.github.io/samples/

11
6
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
11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?