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 為例:http://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) }) ```

第二步:實現客戶端

``` 線上聊天室