WebRTC之信令篇

在WebRTC中,信令發(fā)揮著舉足輕重的作用,但是webrtc工作組并沒有對信令交互進行標(biāo)準(zhǔn)化,留給開發(fā)人員自行選擇。這也導(dǎo)致了信令交互的方案出現(xiàn)了多種,了解這些方案之間的差異,將有助于我們在研發(fā)WebRTC應(yīng)用程序時做出正確的選擇。

1 信令的作用

在實時通信中,信令的作用主要體現(xiàn)在以下幾個方面:

  1. 協(xié)商媒體功能和設(shè)置
  2. 標(biāo)識和驗證會話參與者的身份
  3. 控制媒體會話、指示進度、更改會話和終止會話
  4. 當(dāng)會話雙方同時嘗試建立或更改會話時,實施雙占用分解

以上幾點功能,在WebRTC中,只有第1項是必須功能,第2、3和4項均為可選功能。

1.1 為何沒有建立信令標(biāo)準(zhǔn)

在WebRTC中,要讓兩個(web應(yīng)用程序)瀏覽器之間能夠進行互操作,無需建立標(biāo)準(zhǔn)信令。因為web服務(wù)可以確保兩個瀏覽器通過下載同一份JavaScript代碼,來實現(xiàn)相同的定制信令通信協(xié)議。

Web模型只對極少的組件建立了統(tǒng)一的標(biāo)準(zhǔn),如下圖中,服務(wù)器負責(zé)選擇信令協(xié)議。并確保web應(yīng)用程序或網(wǎng)站的各個用戶支持改協(xié)議。web服務(wù)器A和B無需使用相同的信令協(xié)議,但各自都能夠使用兩個瀏覽器建立媒體會話。

需要了解的是,WebRTC是可以支持與VoIP或視頻系統(tǒng)進行集成的,這個時候由于IP電話或視頻終端為特殊終端,無法運行web應(yīng)用程序。那么要實現(xiàn)與其互操作的唯一方式,就是支持其使用的專用信令協(xié)議,例如SIP或Jingle。

1.2 媒體協(xié)商

WebRTC規(guī)范包含了針對“信令通道”的要求。信令最重要的任務(wù)就在于,在參與對等連接的兩個瀏覽器之間交換會話描述協(xié)議(SDP)對象中包含的信息。SDP包含供瀏覽器中的RTP媒體棧配置媒體會話所需要的全部信息。

  • 媒體類型(音頻、視頻、數(shù)據(jù))
  • 所用的編碼器(Opus、G711等)
  • 用于編解碼器的各個參數(shù)或設(shè)置
  • 有關(guān)寬帶信息
  • 交換候選地址,用于ICE打洞
  • 交換用于SRTP的密鑰材料

1.3 標(biāo)識和身份驗證

使用標(biāo)準(zhǔn)信令協(xié)議(如SIP或Jingle)發(fā)起實時通信時,信令通道將提供參與者的標(biāo)識,并可以選擇進行身份認證。在WebRTC中,除信令之外,還有兩個渠道可用于確定身份。第一種渠道是參見Web應(yīng)用程序的上下文。例如一個web用戶希望與另一個用戶建立會話時,此web應(yīng)用程序?qū)⑵聊幻鳛闃?biāo)識提供給對方,對方用戶只能無條件相信對端提供的標(biāo)識符可信。第二種渠道是查看URL中可能傳遞的標(biāo)識。此URL中包含隨機令牌。通過這種方式建立webrtc會話時,雙方都應(yīng)該知道該令牌標(biāo)識。

WebRTC中定義了另外一種標(biāo)識方法,即通過媒體通道來進行身份確認。在信令交互階段。瀏覽器A生成自己的證書對pub-cert-A,pri-key-A,并產(chǎn)生公鑰的指紋fgrp-A,此指紋作為SDP的一部分,隨信令交互到達瀏覽器B。同樣瀏覽器B也會以相同的方式,將代表自己身份的公鑰指紋隨信令交互傳遞給瀏覽器A。在媒體通道建立階段,瀏覽器A和瀏覽器B開始ICE打洞,然后進行DTLS握手,建立DTLS后,此時瀏覽器B已經(jīng)獲得瀏覽器A的公鑰證書pub-cert-A與其指紋fgrp-A,并進行驗證,確認其是否匹配,從而驗證瀏覽器A的身份確實是會話協(xié)商階段的用戶,同樣瀏覽器A也可以對瀏覽器B進行驗證。

1.4 控制媒體會話

傳統(tǒng)多媒體信令協(xié)議(如SIP 或 Jingle或某種專用協(xié)議)可提供會話呼叫控制。在WebRTC中,雖然需要信令才能發(fā)起或更改媒體會話,但不需要信令來指示狀態(tài)或終止會話。

1.5 雙占用分解

當(dāng)通信會話的雙方同時嘗試建立或更改會話的時候,就會出現(xiàn)雙占用問題。SIP等信令協(xié)議內(nèi)置有雙占用分解功能。

2 信令傳輸

WebRTC信令的傳輸方式通常有三種:HTTP、Websocket和數(shù)據(jù)通道。

2.1 HTTP傳輸

HTTP也可以用于傳輸WebRTC信令。瀏覽器可發(fā)起新的HTTP請求,以便向服務(wù)器發(fā)送信令信息并從中接收信令信息。信令信息可使用GET或POST方法或已應(yīng)答形式傳輸。如果信令服務(wù)器(注意此處之所以為信令服務(wù)器,因為信令服務(wù)器收到的HTTP請求中包含CORS相關(guān)字段,需要信令服務(wù)器進行處理)支持跨域資源共享,則其IP地址可以不同于web服務(wù)器。即web服務(wù)和HTTP信令服務(wù)可以是同一個服務(wù),也可以是兩個獨立的服務(wù)。

2.2 websocket傳輸

WebSocket傳輸允許瀏覽器開通一個與服務(wù)器的雙向連接,此連接最初采用HTTP請求的形式,但是隨后升級為websocket。只要websocket服務(wù)支持CORS(Cross-Origin Resource Sharing),websocket服務(wù)器的地址可以不同于web服務(wù)器。

2.3 數(shù)據(jù)通道傳輸

由于兩個瀏覽器之間建立數(shù)據(jù)通道之后,它就會提供直接的低延遲連接,這非常適合用于傳輸信令。但是由于最初數(shù)據(jù)通道建立時需要單獨的信令機制,因此數(shù)據(jù)通道無法單獨用于傳輸所有的webrtc信令。

3 信令協(xié)議

WebRTC信令協(xié)議的選擇至關(guān)重要,而且不必局限于所選的信令傳輸方式。開發(fā)人員可選擇創(chuàng)建自己的專有信令協(xié)議,采用 SIP 或 Jingle 等標(biāo)準(zhǔn)信令協(xié)議,或者使用通過抽象化處理剝離了信令協(xié)議細節(jié)的庫。

采用專有信令協(xié)議的優(yōu)點在于,它可以非常簡單,并且只有提供應(yīng)用程序所需的功能。如果 WebRTC 對等連接始終僅限于兩個瀏覽器,而不通過中間環(huán)節(jié)或不通向 SIP 或 Jingle VoIP 或者視頻終端,則適合采取這一選項。

下面我們主要介紹一下可能用到的兩種自定義信令的方式,對于專有信息協(xié)議(如SIP或Jingle)就不進行詳細介紹了。

3.1 信令標(biāo)識

為實現(xiàn)標(biāo)識和驗證會話參與者的身份的作用,需要在服務(wù)器中設(shè)置某種路由邏輯。如果瀏覽器和服務(wù)器之間的給定連接可以通過令牌標(biāo)識,則發(fā)送至服務(wù)器的信令消息可包含另一個“服務(wù)器至瀏覽器”連接的令牌。這樣,Web服務(wù)器代碼將在兩個連接之間充當(dāng)代理或用于轉(zhuǎn)發(fā)信息。

3.2 HTTP輪詢

HTTP輪詢是一種簡單的專有信令方案,通過在 JavaScript 或 jQuery 中調(diào)用 XHR,可使 JavaScript 應(yīng)用程序針對Web服務(wù)器生成新的HTTP請求并處理HTTP響應(yīng)。XHR 是一種W3C標(biāo)準(zhǔn)API,XHR JavaScript API 的各個功能組件均受瀏覽器支持,雖然 XHR 的名稱中包含 HTTP請求,但除了發(fā)送 XML 請求之外,它還可以發(fā)送 JSON 或 明文。XHR 可以使瀏覽器生成新的 HTTP 或 HTTPS 請求,例如 GET、PUT、POST等,相應(yīng)的 API 調(diào)用將指定要使用的方法以及IP地址和端口號。針對請求的響應(yīng)將會被返回給 JavaScript。

要使用 XHR 作為 WebRTC 信令通道,Web服務(wù)器需要運行相應(yīng)的應(yīng)用程序,用于通過另一個XHR通道接收HTTP請求并從一個瀏覽器接收信息以代理的形式轉(zhuǎn)發(fā)給另外一個瀏覽器。

為了交換信令信息。每個瀏覽器中運行的JavaScript會定期向信令服務(wù)器發(fā)起HTTP消息輪詢,瀏覽器使用POST方法發(fā)送信息消息,服務(wù)器收到的信令信息包含在發(fā)送給POST的200 OK響應(yīng)中。(請注意,此處HTTP請求均為短連接,每一個消息都是一個新的請求。)

3.3 WebSocket代理

對于用來傳輸 WebRTC 信令的 WebSockets 代理,其使用的服務(wù)器將具有公共的IP地址,兩個對等連接的服務(wù)器均可以訪問。每個瀏覽器都與 websocket代理服務(wù)器建立一個連接,代理服務(wù)器進行消息的轉(zhuǎn)發(fā)。

3.4 信令協(xié)議總結(jié)

方案 服務(wù)器要求 優(yōu)點
WebSocket代理 提供服務(wù)器代碼的websocket服務(wù)器 無需信令基礎(chǔ)架構(gòu)
XML HTTP 提供服務(wù)器代碼的web服務(wù)器 無需基礎(chǔ)信令架構(gòu)
SIP 支持SIP websocket傳輸?shù)?SIP 注冊/ 代理服務(wù)器 易與SIP終端或基礎(chǔ)架構(gòu)互操作,無需無服務(wù)器代碼
Jingle 支持 XMPP websocket傳輸?shù)?XMPP服務(wù)器 易與Jingle終端或基礎(chǔ)架構(gòu)互操作,無需服務(wù)器代碼
數(shù)據(jù)通道 用于建立數(shù)據(jù)通道的websocket或web服務(wù)器 信令延遲短并可以保護信令隱私

4 websocket信令服務(wù)器示例

此處使用js庫 socket.io

var log4js = require('log4js');
var http = require('http');
var https = require('https');
var fs = require('fs');
var socketIo = require('socket.io');

var express = require('express');
var serveIndex = require('serve-index');

var USERCOUNT = 3;

log4js.configure({
    appenders: {
        file: {
            type: 'file',
            filename: 'app.log',
            layout: {
                type: 'pattern',
                pattern: '%r %p - %m',
            }
        }
    },
    categories: {
       default: {
          appenders: ['file'],
          level: 'debug'
       }
    }
});

var logger = log4js.getLogger();

var app = express();
app.use(serveIndex('./public'));
app.use(express.static('./public'));



//http server
var http_server = http.createServer(app);
http_server.listen(80, '0.0.0.0');

var options = {
    key : fs.readFileSync('./public/cert/server-key.pem'),
    cert: fs.readFileSync('./public/cert/server-cert.pem')
}

//https server
var https_server = https.createServer(options, app);
var io = socketIo.listen(https_server);

//服務(wù)端收到連接后的處理函數(shù)
io.sockets.on('connection', (socket)=> {

/*處理此連接上的 message 類型的消息*/
    socket.on('message', (room, data)=>{
        logger.debug('message, room: ' + room + ", data, type:" + data.type);
        socket.to(room).emit('message',room, data);
    });


/*處理此連接上的 join 類型的消息*/
    socket.on('join', (room)=>{
        socket.join(room);
        var myRoom = io.sockets.adapter.rooms[room]; 
        var users = (myRoom)? Object.keys(myRoom.sockets).length : 0;
        logger.debug('the user number of room (' + room + ') is: ' + users);

        if(users < USERCOUNT){
            socket.emit('joined', room, socket.id); //發(fā)給除自己之外的房間內(nèi)的所有人
            if(users > 1){
                socket.to(room).emit('otherjoin', room, socket.id);
            }
        
        }else{
            socket.leave(room); 
            socket.emit('full', room, socket.id);
        }
        //socket.emit('joined', room, socket.id); //發(fā)給自己
        //socket.broadcast.emit('joined', room, socket.id); //發(fā)給除自己之外的這個節(jié)點上的所有人
        //io.in(room).emit('joined', room, socket.id); //發(fā)給房間內(nèi)的所有人
    });

/*處理此連接上的 leave 類型的消息*/
    socket.on('leave', (room)=>{

        socket.leave(room);

        var myRoom = io.sockets.adapter.rooms[room]; 
        var users = (myRoom)? Object.keys(myRoom.sockets).length : 0;
        logger.debug('the user number of room is: ' + users);

        //socket.emit('leaved', room, socket.id);
        //socket.broadcast.emit('leaved', room, socket.id);
        socket.to(room).emit('bye', room, socket.id);
        socket.emit('leaved', room, socket.id);
        //io.in(room).emit('leaved', room, socket.id);
    });

});

https_server.listen(443, '0.0.0.0');
console.log("start singal");


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容