WebRTC 中 Websocket 的使用

語言: CN / TW / HK

1. WebSocket 概念

WebSocket 是一種在單個 TCP 連接上進行全雙工通信的網絡協議。意為:經過一次 TCP 握手就可以直接創建持久性連接,進而可實現服務端和客户端雙向數據傳輸。Websocket 的協議標識是 ws 和 wss。

Websocket 的應用場景:

  • 在線聊天

  • 協作文檔編輯

  • 大型多人在線遊戲

  • 股票交易應用

  • WebRTC

2. 為什麼需要 WebSocket 協議

2.1 WebSocket 的出現主要是為了彌補 HTTP 半雙工通信的缺陷。

在 Websocket 沒有出現之前,為了讓 HTTP 能夠實現即時通信,前輩們也做了一些研究,常用的有三種方法:

1. HTTP 輪詢

HTTP 輪詢(polling):在固定的時間間隔,由瀏覽器向服務器發起 HTTP 請求,無論服務器中的數據有沒有更新,都會給客户端作出響應。

但如果知道信息交付的精確間隔,那麼輪詢也是一個好的方案,但對於一些實時的數據是不能預測的,所有就會導致發出一些不必要的請求。

2. 長輪詢

長輪詢(long polling):客户端向服務端請求信息,並在設定的時間段內打開一個連接。服務器如果沒有任何信息,會保持請求打開,直到有客户端可用的信息,或者直到指定的超時時間用完為止。

長輪詢中客户端必須頻繁地重連到服務器以讀取服務端的信息,會增大服務端到壓力。

3. 流化技術

客户端向服務端發起一個長連接請求,服務端收到請求後響應它並不斷更新連接狀態,以確保連接在客户端與服務端之間一直有效。服務端可以通過這個連接將數據主動推送到客户端。

但存在一個問題:每當服務器有需要交付給客户端的信息時,它就會更新響應,但是服務器從不發出完成 HTTP 響應,從而導致連接一直打開,在這種情況下,代理和防火牆可能會緩存一個響應,就會導致信息交付的延遲增加。

以上三種方法都實現了近乎實時的通信,但都涉及 HTTP 請求和響應,當然也包含了許多附加和不必要的延遲,此外,在每一種情況下,客户端必須主動給服務器發送消息,且客户端都必須等待請求返回,才能發出後續的請求,再一次增加了延遲。

2.2 Websocket 與 HTTP 有着良好的兼容性

默認端口是 80 和 443, 並且握手階段採用 HTTP 協議,因此握手的時候不容易屏蔽,能通過各種的 HTTP 代理。

3. WebSocket 通信原理

以七牛 WebRTC Demo 為例:https://demo-rtc.qnsdk.com/

詳解:

每個 WebSocket 連接都開始於一個 HTTP 請求,這個請求和其他請求類似,但是 Websocket 連接請求中包含一個特殊的首標,Upgrade:Websocket,意為:客户端想將 HTTP 協議升級為 Websocket 協議。如果服務端同意,則響應 Connection:Upgrade,同時 101 Switching Protocols 也表示協議切換成功,這個過程叫做初始握手

但為了成功地完成握手,Websocket 服務器必須根據客户端請求消息中的 Sec-WebSocket-Key,響應 SHA-1 的信息摘要,即:Sec-WebSocket-Accept 。其中:Sec-WebSocket-Key 是一個隨機字符串,服務端接收到 Key 之後,會對其進行加密,並進行 base-64 編碼,然後將結果響應給客户端;客户端將 Key 使用同樣的加密算法進行加密並進行 base-64 編碼,當得到的值與服務端響應的值保持一致時,表示真正的握手成功。

至此,HTTP 已經完成了它所有的工作,接下來就是完全按照 Websocket 協議進行通信。

4. WebRTC 中 Websocket 的使用

在 WebRTC 中 Websocket 充當信令服務器,那何為信令服務器?信令可理解為信息的傳遞或者命令的執行,主要是傳輸用户的一些信息。在 WebRTC 中如果沒有信令服務器,WebRTC 之間是不能夠通信的。

藍色區域表示發送端(Caller)和接收端(Callee),如果兩者想要傳遞媒體數據,那麼有兩個信息必須經過信令服務器交換;

1)媒體信息:通過 SDP 協議進行交換,SDP 是一個描述多媒體連接內容的協議,其中包含了分辨率、編解碼方式、格式、是否支持音頻、視頻等。例如,Caller 想給 Callee 發一個 H264 的視頻,需要先問一下 Callee 能不能解 H264 的視頻,如果可以解碼,則可以通信;如果 Callee 只能解 H265 的視頻,則不可通信。

2)網絡信息:通常指的是 ip 地址、端口、以及數據存放地址,我們稱之為 ICE,這是一個基於 offer/answer 模式解決 NAT 穿越的協議集合。在 ICE 中主要包含 STUN+TURN 主要協議。當 Callee 想要接收數據時,需要將所有的網絡相關的信息傳到信令服務器,信令服務器再轉發給 Caller,Caller 拿到信息之後,發現處於同一個局域網,則可通信,如果不在同一個局域網,則通過 TURN 協議進行 NAT 穿越,再利用 Relay 轉發,兩者即可通信。

因為 TCP 的超時時間為 60s,如果要保持長連接的話,最好加一個 ping/pong 的心跳檢測,就是服務端給客户端發一個 ping 的消息(綠色),客户端再給服務端發送一個 pong 的消息(紅色),就是在 server 端加一個定時調用函數setInterval,即可實現

setInterval(() => { connect.send('ping'); }, 3000);

5. 使用 node.js 實現簡易聊天室

第一步:實現服務器

安裝第三方依賴庫:nodejs-websocket

具體實現如下

``` const ws = require('nodejs-websocket') const PORT = 3003 const TYPE_ENTER = 0 const TYPE_LEAVE = 1 const TYPE_MSG = 2

//1. 記錄當前連接上來的總的用户數量 let count = 0 //2.conn每個連接到服務器的用户,都會有一個conn const server = ws.createServer(conn => { console.log('有用户進來') count++ conn.userName = 用户${count}

broadcast({
    type:TYPE_ENTER,
    msg:`${conn.userName}進入了聊天室`,
    time:new Date().toLocaleTimeString()
})

//每當接收到用户傳遞過來的數據,這個text事件會被觸發
conn.on('text',data =>{
    console.log('接受到用户的數據',data)  
    broadcast({
        type:TYPE_MSG,
        msg:data,
        time:new Date().toLocaleTimeString()
    })
})

conn.on('close',() => {
    console.log('連接斷開了')
    count--
    broadcast({
        type:TYPE_LEAVE,
        msg:`${conn.userName}離開了聊天室`,
        time:new Date().toLocaleTimeString()
    })

})
conn.on('error',() => {
    console.log('用户連接異常')
})

})

// 通過廣播,給所有的用户發送消息 function broadcast(msg){ //server.connections:表示所有用户 server.connections.forEach(item => { item.send(JSON.stringify(msg)) }) } server.listen(PORT,() => { console.log('websocket服務啟動成功了,監聽了端口' + PORT) }) ```

第二步:實現客户端

``` 在線聊天室