WebRTC 入門教程(二)| WebRTC信令控制與STUN/TURN服務器搭建
- 四月 4, 2019
作者:李超,音視頻技術專家。本文首發於 RTC 開發者社區,歡迎在社區留言與作者交流。 https://webrtc.org.cn/webrtc-tutorial-2-signaling-stun-turn/
本文將向大家介紹兩個方面的知識:
- WebRTC信令控制
- STUN/TURN服務器的搭建
在前面的文章中已經向大家介紹了如何構建信令服務器。但構建的信令服務器是如何工作的?那些消息需要信令服務器控制和中轉?這些此前並沒有做詳細的說明,而本文將對這些問題做詳細的討論。
另一方面,在真實的網絡中,WebRTC是如何進行NAT穿越的呢?如果穿越不成功,我們又該如何保證用戶服務的呢?這些知識也將在本文中給出答案。
信令
WebRTC 信令控制的架構圖如下所示:
信令服務器用於交換三種類型的信息:
- 會話控制消息:初始化/關閉,各種業務邏輯消息以及錯誤報告。
- 網絡相關:外部可以識別的IP地址和端口。
- 媒體能力:客戶端能控制的編解碼器、分辯率,以及它想與誰通訊。
下面我們就來詳細討論一下這三類消息:
會話控制消息
會話控制消息比較簡單,像房間的創建與銷毀、加入房間、離開房間、開啟音頻/關閉音頻、開啟視頻/關閉視頻等等這些都是會話控制消息。
對於一個真正商業的WebRTC信令服務器,還有許多的會話控制消息。像獲取房間人數、靜音/取消靜音、切換主講人、視頻輪詢、白板中的畫筆、各種圖型等等。但相對來說都是一引起比較簡單的消息。
在我們之前的例子中,服務端只處理了一個會話消息 create or join,即房間的創建與加入消息。代碼如下:
- ...
- socket.on('create or join', function(room) {
- var clientsInRoom = io.sockets.adapter.rooms[room];
- var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;
- if (numClients === 0) {
- socket.join(room);
- logger.debug('Client ID ' + socket.id + ' created room ' + room);
- socket.emit('created', room, socket.id);
- } else if (numClients === 1) {
- io.sockets.in(room).emit('join', room);
- socket.join(room);
- socket.emit('joined', room, socket.id);
- io.sockets.in(room).emit('ready');
- } else { // max two clients
- socket.emit('full', room);
- }
- });
- ...
該代碼的邏輯非常簡單,當收到 create or join 消息后,判斷房間里當前人數,如果房間里的人數為 0,說明是第一個人進來,此時,需要向連接的客戶端發送 created 消息;如果房間里的人數為 1,說明是第二個人進來,需要向客戶端發送 joined消息;否則發送 full 消息,說明房間已滿,因為目前一個房間最多只允許有兩個人。
網絡信息消息
網絡信息消息用於兩個客戶端之間交換網絡信息。在WebRTC中使用 ICE 機制建立網絡連接。
在WebRTC的每一端,當創建好 RTCPeerConnection 對象,且調用了setLocalDescription 方法后,就開始收集 ICE候選者 了。
在WebRTC中有三種類型的候選者,它們分別是:
- 主機候選者
- 反射候選者
- 中繼候選者
主機候選者,表示的是本地局域網內的 IP 地址及端口。它是三個候選者中優先級最高的,也就是說在 WebRTC 底層,首先會償試本地局域網內建立連接。
反射候選者,表示的是獲取 NAT 內主機的外網IP地址和端口。其優先級低於 主機候選者。也就是說當WebRTC償試本地連接不通時,會償試通過反射候選者獲得的 IP地址和端口進行連接。
其結構如下圖所示:
在上面這幅圖中可以看到,WebRTC通過 STUN server 獲得自己的外網IP和端口,然后通過信令服務器與遠端的WebRTC交換網絡信息。之后雙方就可以償試建立 P2P 連接了。
以上就是我們通常所說的 P2P NAT 穿越。在WebRTC內部會探測用戶的 NAT 類型,最終采用不同的方法進行 NAT 穿越。不過,如果雙方都是 對稱NAT 類型,是無法進行 P2P NAT 穿越的,此時只能使用中繼了。
中繼候選者,表示的是中繼服務器的IP地址與端口,即通過服務器中轉媒體數據。當WebRTC客戶端通信雙方無法穿越 P2P NAT 時,為了保證雙方可以正常通訊,此時只能通過服務器中轉來保證服務質量了。
所以 中繼候選者的優先級是最低的,只有上述兩種候選者都無法進行連接時,才會使用它。
在 WebRTC 信令服務器端,收到網絡消息信令,即 message 消息時,不做任何處理,直接轉發。代碼如下:
- socket.on('message', function(message) {
- socket.broadcast.emit('message', message);
- });
客戶端接收到 message 消息后,會做進一步判斷。如果消息類型為 candidate,即 網絡消息信令時,會生成 RTCIceCandidate 對象,並將其添加到 RTCPeerConnection 對象中,從而使 WebRTC 在底層自動建立連接。 其代碼如下:
- socket.on('message', function(message) {
- ...
- } else if (message.type === 'candidate') {
- var candidate = new RTCIceCandidate({
- sdpMLineIndex: message.label,
- candidate: message.candidate
- });
- pc.addIceCandidate(candidate);
- } else if (...) {
- ...
- }
- });
交換媒體能力消息
在WebRTC中,媒體能力最終通過 SDP 呈現。在傳輸媒體數據之前,首先要進行媒體能力協商,看雙方都支持那些編碼方式,支持哪些分辨率等。協商的方法是通過信令服務器交換媒體能力信息。
WebRTC 媒體協商的過種如上圖所示。
- 第一步,Amy 調用 createOffer 方法創建 offer 消息。offer 消息中的內容是 Amy 的 SDP 信息。
- 第二步,Amy 調用 setLocalDescription 方法,將本端的 SDP 信息保存起來。
- 第三步,Amy 將 offer 消息通過信令服務器傳給 Bob。
- 第四步,Bob 收到 offer 消息后,調用 setRemoteDescription 方法將其存儲起來。
- 第五步,Bob 調用 createAnswer 方法創建 answer 消息, 同樣,answer 消息中的內容是 Bob 的 SDP 信息。
- 第六步,Bob 調用 setLocalDescription 方法,將本端的 SDP 信息保存起來。
- 第七步,Bob 將 anwser 消息通過信令服務器傳給 Amy。
- 第八步,Amy 收到 anwser 消息后,調用 setRemoteDescription 方法,將其保存起來。
通過以上步驟就完成了通信雙方媒體能力的交換。
上以就是信令服務器應該處理的所有消息,這些消息組成了信令服務器最基本的信令,每一個都必不可少,否則的話雙方就無法進行最終的通信了。
在WebRTC 通訊時,光有信令是遠遠不夠的。因為 WebRTC真正要傳輸的是媒體數據,信令只不過是其中的一部分。在WebRTC中他會盡可能的通過P2P進行數據的傳輸,但在 P2P穿越不成功時怎么辦呢?
那就需要通過媒體中繼服務器進行媒體數據的轉發,下面我們就來看一下如何搭建媒體中繼服務器吧。
搭建 STUN/TURN
在公網搭建一套 STUN/TURN 服務並不難。首先要有一台雲主機,雲主機的獲我就不做介紹了,大家去某個雲廠商購買就好了。
目前比較流行的 STUN/TURN 服務器是 coturn,使用它搭建 STUN/TURN 服務非常的方便。
下面我們就來看一下它的基本步驟:
- 獲取 coturn 源碼
- git clone https://github.com/coturn/coturn.git
- 編譯安裝
- cd coturn
- ./configure --prefix=/usr/local/coturn
- sudo make -j 4 && make install
- 配置 coturn
網上有很多關於 coturn 的配置文章,搞的很復雜。大多數人都是從網上拷貝轉發的,其中有很多錯誤。其實只要使用 coturn 的默認設置就可以了,我這里整理了一份,如下:
- listening-port=3478 #指定偵聽的端口
- external-ip=39.105.185.198 #指定雲主機的公網IP地址
- user=aaaaaa:bbbbbb #訪問 stun/turn服務的用戶名和密碼
- realm=stun.xxx.cn #域名,這個一定要設置
所以,只需將上面 4 行配置項寫入到 /usr/local/coturn/etc/turnserver.conf 配置文件中,你的 stun/turn 服務就配置好了。
- 啟動 stun/turn 服務
- cd /usr/local/coturn/bin
- turnserver -c ../etc/turnserver.conf
- 測試 stun/turn 服務
打開 trickle-ice ,按里面的要求輸入 stun/turn 地址、用戶和密碼后就可以探測stun/turn服務是否正常了。
以我們的配置為例,輸入的信息分別是:
- STUN or TURN URI 的值為: turn:stun.xxx.cn
- 用戶名為: aaaaaa
- 密碼為: bbbbbb
測試的結果如下圖所示:
從上圖我們可以看到該服務提供了 stun(srflx)和turn(relay)兩種服務。
STUN/TURN布署好后,我們就可以使用它進行多媒體數據的傳輸了,再也不怕因為 NAT 和防火牆的原因導致雙方無法通信的問題了。
小結
本文首先向大家詳細介紹了 WebRTC 三種類型信令消息的控制與交換。然后給出了 STUN/TURN 服務器的布署、配置以及如何進行測試。
這里需要特別強調的是,STUN/TURN的布署雖然非常簡單,但像 WebRTC 一樣,其背后的原理確很復雜。由於篇幅的原因,我這里並沒有向大家做詳細的介紹,感興趣的同學可以將其做為了一切入點進行深入的研究。