WebRTC 之點對點連接——瀏覽器

WebRTC 的精髓——點對點連接

上一篇文章中,主要講了瀏覽器怎樣獲取用戶設備上的視頻流,並且顯示在 HTML5 <video> 標籤中。這一篇文章則是讓這一切變得有用起來:把視頻流發送到另一位用戶的瀏覽器上。WebRTC 特有的點對點連接,可以讓服務器不必中轉大量的視頻數據,讓通訊的速度、私密性得到更好的保障。這是 WebRTC 相對於 WebSocket 等技術最大的優勢,也就是它存在的根本。

怎樣建立點對點連接

要建立一個點對點連接,並在其上傳送視頻內容,我們需要兩個瀏覽器互相交換以下信息:

1.視頻流的元數據,包括分辨率和編碼格式等
2.各自的網絡連接情況,包括用於 NAT 穿透的信息

WebRTC 用於實現了以上信息交換,提供給瀏覽器 JavaScript 平臺的 API 就是 RTCPeerConnection

爲了完成以上第 1 種信息的交換,我們用 RTCPeerConnection 的 createOffer() 方法生成一個 Offer,它是以 SDP(Session Description Protocol,會話描述協議)格式傳送的。對方收到 Offer 後,應該生成一個 Answer 併發回,這個 Answer 同樣是 SDP 格式的。通信的雙方通過調用setLocalDescription() 方法,把自己生成的 SDP 設置成本地描述;通過調用setRemoteDescription() 方法,把對方發給自己的 SDP 設置成遠程描述。以上的這個過程,被統稱爲JSEP(JavaScript Session Establishment Protocol,JavaScript 會話建立協議)。


JSEP 結構(via html5rocks.com)

對於以上第 2 種信息的交換,則是通過 ICE(Interactive Connectivity Establishment,交互式連接建立)完成的。對於點對點連接最簡單的設想是,大家都連接在一個網絡中,只要雙方都知道對方的 IP 地址,我就可以直接發送數據。但現實永遠不會這麼簡單:如今的網絡世界中,絕大部分設備並不是直接連接到互聯網上,具有一個公網 IP 地址,而是處在層層的路由器和防火牆的背後,這也就使得直接建立連接變得不可能。不過,如果雙方都向一個公網上的服務器發送一個請求,這臺服務器可以獲取到雙方的公網地址,這樣就可以讓雙方知曉怎樣和對方進行通訊。這就是 STUN 服務器。


信令交換與 STUN/TURN 服務器(via html5rocks.com)

當雙方完成了 Offer 和 Answer 的交換後,RTCPeerConnection 便利用 STUN 服務器收集 ICE 候選,也就是雙方建立連接的多個可能途徑,然後在這些候選中挑選最優化的一個,用以建立點對點連接。

STUN 還有一個擴展,即 TURN 服務器。除了實現 STUN 的全部功能外,當雙方由於某種原因(如防火牆)還是沒法建立點對點連接時,TURN 服務器可以起到中轉的作用,讓雙方可以繞過防火牆進行通訊(事實上絕大多數防火牆被配置爲允許從內部向外主動發起的連接)。

實例代碼

// 建立一個 RTCPeerConnection 實例,這裏設置了 STUN 或 TURN 服務器
var servers = {
  'iceServers': [
    {
      'url': 'stun:turn.mywebrtc.com'
    },
    {
      'url': 'turn:turn.mywebrtc.com',
      'credential': 'siEFid93lsd1nF129C4o',
      'username': 'webrtcuser'
    }
  ]
};
peerConnection = new RTCPeerConnection(servers);

// 交換 ICE 候選,通過 WebSocket 發送
peerConnection.onicecandidate = function (e) {
  if (e.candidate) {
    console.log(['ICE candidate', e.candidate]);
    socket.emit('message', roomToken, {
      'candidate': e.candidate
    });
  }
};

// 接收到對方添加的視頻流時,顯示在本地的 <video> 標籤中
peerConnection.onaddstream = function (e) {
  remoteMediaStream = e.stream;
  remoteVideo.src = URL.createObjectURL(remoteMediaStream);
};

// 在這裏添加上一篇文章中獲取到的本地視頻流
peerConnection.addStream(localMediaStream);

// 包裝一個 Offer
peerConnection.createOffer(gotLocalDescription, handleError);

// 有了 Offer,通過 WebSocket 發送給對方
function gotLocalDescription(desc) {
  peerConnection.setLocalDescription(desc);
  socket.emit('message', roomToken, {
    'sdp': desc
  });
}

// 在 WebSocket 中接收到信息時
socket.on('message', function (message, socketId) {
if (message.sdp) {
    // 接收到 Offer 時,創建 Answer 併發送
    var desc = new RTCSessionDescription(message.sdp);
    peerConnection.setRemoteDescription(desc, function () {
      peerConnection.createAnswer(gotLocalDescription, handleError);
    }, handleError);
  } else {
    // 接收到 ICE 候選時,讓 RTCPeerConnection 收集它,稍後它將在這些候選方式中挑選最佳者建立連接
    // 注意:RTCPeerConnection 要在 setLocalDescription 後才能開始收集 ICE 候選
    peerConnection.addIceCandidate(new RTCIceCandidate(message.candidate));
  }
});

下期預告

本文簡述了點對點連接的建立過程中,雙方信息交換的流程。

事實上,在 WebRTC 的技術規範中並沒有規定這些信息交換要通過什麼途徑進行,而是把選擇的自由留給留給上層的應用程序。在開發 Web App 時,我們可能最先想到的是 WebSocket,但是也可以採用 SIP 或者 Jingle,或者 XMPP 信息服務比如 OpenFire 之類,等等。在本文中,我們使用了 WebSocket 實現信令交換。

信令服務的任務並不止於連接的建立過程。我們還需要把諸如有人加入聊天、有人掛斷這樣的信息通知給所有客戶端。這些信息既可以用與建立連接時相同的機制進行交換,也可以用 WebRTC RTCDataChannel,這是 WebRTC 的數據傳送通道,但這個通道只能在點對點連接建立好以後才能使用,也就是說它不能代替 WebSocket 等,但可以在連接建立後把信令交換的任務接管過來。

WebRTC 之服務器 將會介紹如何使用 node.js 和 socket.io 建立一個 WebSocket 服務器,以提供信令服務;以及如何搭建 STUN/TURN 服務器。


https://hyjk2000.github.io/2015/05/16/webrtc-peer-connection/


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章