30-26之 WebRTC 的 P2P 即時通信與小範例

正文開始

在很前面的文章中,咱們有簡單的介紹如何使用 WebRTC 來採集聲音與影像,但那時只是很簡單的介紹一下而以,所以接下來的幾篇文章,咱們將要來深入的了解 WebRTC。

這篇文章將要介紹幾個 WebRTC 的基概念,大約分成以下幾個章節:

  • WebRTC 的誕生與內部架構。
  • WebRTC 所支援的語音視編碼與傳輸協議。
  • WebRTC 提供的基本 P2P 功能。
  • WebRTC 的簡單通訊實作。

WebRTC 的誕生與內部架構

首先在 Web 通信的世界中,基本上都是所謂的 C/S 架構,也就是所謂的 client 與 server 架構,通常 client 要取資料時就是發送一個請求給 server 然後它會回傳資料回去,其中 ajax 的出現讓我們更能以少量的資源來取得資料,在這階段時都部份都是單向溝通,也就是 client 請求 server。

而在二階段能,人們開始有種需求,例如股票報價網站,人們希望可以看到當有股價變動時,網頁可以也同時更動,這時如果用上面那種模式,那就只能 client 定時的去 server 拿資料,也就是咱們所謂的輪詢,但這種方法很明顯的非常的浪費資源,你可以呼叫 server 十次,但只有一次才真的有新的資料。而這時webSocket就用來解決這向事情,它提供了雙向溝通功能,server 就可以透過它,來將資料推給 client。

基本上以上已經解決了 client 與 server 的雙向互動,但這時人們又在想,假設我是做個一對一的聊天工具,那為什麼還需要 server 呢? 不能直接 client 與 client 進行溝通就好呢 ? WebRTC 就是可以幫助我們完成的工具,它就是用來專門處理瀏覽器與瀏覽器之間的即時溝通。

備註:雖然說是 cleint 與 client 直接進行溝通,但不是說不需要 server,後面會說明。

WebRTC 的存在目的,就是為了讓瀏覽器不需要認何 plugin 就可以快速的開發出 P2P 語音或視頻對話 。

WebRTC 的內部架構與所提供的 API

首先在咱們來看看 WebRTC 的基本架構,如下圖。

圖片來源:官網

最外層紫色的地方就是我們所使用的 API 部份,基本上可以分三種:

  • getUserMedia: 用來處理音視串流採集。 (採集聲音或影像)。
  • RTCPeerConnection: 用來建立兩個瀏覽器之間的直接通訊。 (建立與管理 p2p 連線)
  • RTCDataChannel: 負責用來傳送資料。(操作那條 p2p 連線)

然後藍色實線那層的 WebRTC C++ API 是專門給瀏覽器開發商更容易的實作 WebRTC 標準的 WebAPI。

最後藍色虛線那層由各瀏覽器開發商自行定義實作。

備註

關於 getUserMedia 的相關使用,可以參考筆者的以下兩篇文章。

30-07之Web 如何進行語音與影像採集 ? 30-08之 WebRTC 採集的詳細說明與聲音的加工

WebRTC 所支援的語音視編碼與傳輸協議

在開始傳輸聲音之前,我們需要先進行編碼,接下來就是進行封裝,而封裝時也需要據傳輸協議才能決定要封裝成什麼型式來送貨。接下來這章節就簡單的說明一下 WebRTC 所支援的編碼與傳輸協議。

語音編碼

比較詳細的說明可至筆者的這篇文章『30-03之聲音的編碼與壓縮

Opus

這個編碼只要記得他是瑞士刀就好。

iSac

它是一個提供寬帶 (wideband) 與超寬帶 (wideband) 的語音編碼器,大部份使用在 VoIP 與串流應用中,為 WebRTC 預設的語音編碼器。(目前是屬於 google 的)

備註: 上面說的寬帶與超寬帶的意思是指他的採樣率的意思,寬帶是指 16KHz 與超寬帶指的是 32KHz。

iLBC

它是一個提供窄帶的語音編碼器,也是大部都使用在 VoIP 與串流應用中,它的採樣率為 8HZ。

視頻編碼

比較詳細的說明可至筆者的這篇文章『30-05之影像的編碼與壓縮

VP8

它是 Google 收購的 On2 所開發的視頻編碼,它基本上會封裝在 .webm 格式中

VP9

它就是 VP8 的進化版。

WebRTC 所使用的通訊協議

它使用 RTP/RTCP 協議

此協議詳細內容請看筆者的此篇文章『30-12之 RTP/RTCP 傳輸協議』。

備註: 別忘了它是傳輸層協議 備註: 它可以選擇用 UDP(預設) 或 TCP

這裡問一個問題。

為什麼 WebRTC 要使用 RTP 協議呢 ?

  • RTSP:RTSP 大部份是為了控制串流而使用它,而 WebRTC 目的是建立 P2P 的即時影音溝通,所以不太會選擇使用 RTSP。
  • RTMP:Web 現在大部份不支援 Flash,所以這也不用,而且真要要幹的話,就代表雙方的瀏覽器都要可以解析 RTMP,那這樣只是找麻煩。
  • HTTP 系列:如果有使用就代表雙方瀏覽器要有 HTTP Server 功能,不然對方收到我的 HTTP 請求如何解析,而且 HTTP 本來就是 C/S 架構的設計,要拿來用 C/C 架構,找自已的麻煩。

不過我覺得最大的理由在於,某此方面來說 WebRTC 已經可以算是應用層的協議,它讓雙方的瀏覽器都定義好了一定的流程來完成 P2P 溝通這件事情,因此它接下來應該只要在選擇使用什麼傳輸層協議來進行溝通就行了。

WebRTC 提供的基本 P2P 功能


要建立一個 WebRTC 的 P2P 基本應用如下圖,你會發現事實上還有一個 Server,但它不是說是 P2P 連線嗎 ?

嗯沒錯,但它是指聲音或影像直接的進行 P2P 傳送,而不是需要經過 Server 來將聲音傳出去。

那這個 Server 是要做什麼用的 ? 在 WebRTC 雙方要建立連線前,誰都不認識對方,這也代表雙方都不知對方在那,而這個 Server 就是讓他們互相認識的交誼廳 ,這在 WebRTC 中又被稱為 Signaling Server 。

建立連線的過程如下圖。

  1. 用戶 A 向 Server 發送與 B 進行會話。
  2. Server 將會話請求發送給用戶 B。
  3. 用戶 B 向 Server 進行應答,並回報 OK。
  4. Server 向用戶 A 發送 用戶 B 的相關資訊。
  5. 雙方開始建立連線。

WebRTC 的簡單通訊實作

這裡基本上分為兩部份程式碼,一段是 client 端的,另一端為 server 端的。

Server

這一段是建立 Signaling Server,我們使用 nodejs 的 express 來建立,然後我們還有使用到peer這個套件來幫助我們可以更輕鬆的使用 WebRTC 的 P2P 操作。

下面這段程式碼中,事實上只做了兩件事。

  1. 建立 client 的頁面 (註:這裡使用/a/b不同的頁面來模擬不同的用戶)
  2. 建立 client 的對話 (註:事實上詳細處理的都已經包在 peer 套件裡面了)
const express = require('express');
const app = express();
const expressPeerServer = require('peer').ExpressPeerServer;
const server = app.listen(9000);

const peerserver = expressPeerServer(server);

app.use('/api', peerserver);

app.get('/a', function(req, res){
    res.sendfile(__dirname + "/index-clientA.html");
});

app.get('/b', function(req, res){
    res.sendfile(__dirname + "/index-clientB.html");
});

peerserver.on('connection', (id) => {
    console.log(`A client connected : ${id}`);
})

peerserver.on('disconnect', (id) => {
    console.log(`A client say ~ bye bye : ${id}`);
});

Client

A 用戶

其中比較需要注意的是在使用new Peer(‘A’)時,這裡面所代的 A 就代表你這用戶的編碼,別人如果要和你進行連線時,就需要使用這個編碼,但是這不是說任何人都可以和你連線,必須是要在此會話中,才能進行連線。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/peerjs/0.3.9/peer.min.js"></script>
    <script>
        const peer = new Peer('A', { host: 'localhost', port: 9000, path: '/api' });

        peer.on('open', function (id) {
            console.log('My peer ID is: ' + id);
        });

        peer.on('connection', (conn) => {
            conn.on('open', () => {
                // 有任何人加入這個會話時,就會觸發
                console.log(`${conn.peer} is connected with me`);
            });
            conn.on('data', function (data) {
                // 當收到訊息時會執行
                console.log(`${conn.peer} : ` + data);
                conn.send('HI I am A');
            });
        });
    </script>
</body>

</html>

B 用戶

這個是 B 用戶,一但它建立連線後,就會發送與 A 會話的請求peer.connect(‘A’),然後連線成功後會在發送一段訊息給 A。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/peerjs/0.3.9/peer.min.js"></script>
    <script>
        var peer = new Peer('B', { host: 'localhost', port: 9000, path: '/api' });
        var conn = peer.connect('A');

        peer.on('open', function (id) {
            console.log('My peer ID is: ' + id);
        });

        conn.on('open', () => {
            // 與 A 連線後,會發送以下訊息給 A。    
            conn.send('Hi I am B');
        });

        conn.on('data', (data) => {
            // 送到某人發送的訊息。
            console.log(`${conn.peer} : ` + data);
        });

    </script>
</body>

</html>

執行

全部程式碼中此『傳送門

1. 安裝 npm 套件

npm install

2. 執行 server

node broker-server.js

3. 在瀏覽器先開啟 A 與在開啟 B 用戶

127.0.0.1:9000/a
127.0.0.1:9000/b

4. 執行結果

A 用戶的畫面

B 用戶的畫面

結論

本篇文章中咱們學習了 WebRTC 的基本概念與架構,並且在簡單的實作一個 P2P 通信範例,而接下來的文章中,咱們要來討探一下上面有說到的 Signaling Server 的一些事情。

參考資料

comments powered by Disqus