實時通信協(xié)議
本文旨在簡要解釋如何在Web上實現(xiàn)客戶端/服務(wù)器和客戶端/客戶端之間的實時通信,以及它們的內(nèi)部工作原理和最常見的用例。

TCP vs UDP
TCP和UDP都位于OSI模型的傳輸層,負責在網(wǎng)絡(luò)上傳輸數(shù)據(jù)包。它們之間的主要區(qū)別在于,TCP在傳輸數(shù)據(jù)之前會打開一個專用連接,并確保所有數(shù)據(jù)包都到達目的地,而UDP則不會。這使得TCP連接速度較慢,但同時更可靠,因為它確保數(shù)據(jù)的到達,而UDP可能更快,但在傳輸過程中可能會丟失一些數(shù)據(jù)包。
這個概念在選擇實時通信技術(shù)時需要牢記,因為它們可能使用TCP或UDP作為傳輸層協(xié)議,具有各自的優(yōu)勢和劣勢。例如,如果您正在開發(fā)一個視頻會議平臺,用戶更希望彼此之間的交互更快,而丟失一些數(shù)據(jù)包是可以接受的。
WebSockets
WebSocket是一種HTTP升級技術(shù),它提供了基于TCP的持久全雙工、雙向連接。它被設(shè)計為客戶端/服務(wù)器連接,允許它們隨時相互發(fā)送數(shù)據(jù)。

1*1CUCOOXEIhcDzBWMDDQb6g.png
握手
為了建立WebSocket連接,客戶端必須向服務(wù)器發(fā)送一個HTTP 握手 請求以切換協(xié)議:
GET /chat HTTP/1.1
Host: my-awesome.server.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: s3TTHMbDL1HtLzh1GKh12t==
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Version: 13
Origin: http://my-awesome.server.com如果滿足要求,服務(wù)器將以HTTP Upgrade 101 Switching Protocols 響應(yīng)來回應(yīng),如果不滿足要求,則返回HTTP錯誤:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat請注意,客戶端請求包含一個帶有Base64編碼的隨機字節(jié)值的 Sec-WebSocket-Key,而服務(wù)器則回復(fù)一個帶有請求密鑰的哈希值的 Sec-WebSocket-Accept 頭。這是為了防止緩存代理重新發(fā)送先前的WebSocket連接。
實現(xiàn)
一旦握手被接受,客戶端已切換協(xié)議,客戶端可以向服務(wù)器發(fā)送數(shù)據(jù),服務(wù)器也可以向客戶端發(fā)送數(shù)據(jù)。
在客戶端中,使用瀏覽器的本機JavaScript引擎,由服務(wù)器發(fā)送的傳入數(shù)據(jù)將由事件函數(shù)處理,一旦數(shù)據(jù)到達,這些事件函數(shù)將被觸發(fā)。
在服務(wù)器端,在大多數(shù)情況下,這也是處理客戶端發(fā)送的數(shù)據(jù)的方式,但也可以通過永不結(jié)束的循環(huán)來處理數(shù)據(jù)。
// HTTP握手
const socket = new WebSocket('ws://my-awesome.server.com');
socket.onopen = (event) => {
// 連接建立時觸發(fā)
socket.send('客戶端發(fā)送到服務(wù)器的數(shù)據(jù)');
});
socket.onmessage = (event) => {
// 服務(wù)器發(fā)送數(shù)據(jù)時觸發(fā)
console.log(event.data);
});
socket.onclose = (event) => {
// WebSocket連接關(guān)閉時觸發(fā)
console.log('連接關(guān)閉')
});預(yù)期使用案例
當您需要低延遲的實時連接時,WebSocket是一種非常強大的協(xié)議,基于Web的游戲、聊天應(yīng)用程序是可以使用此技術(shù)的很好的示例,因為在客戶端之間進行通信非常簡單:客戶端A可以通過服務(wù)器向客戶端B發(fā)送消息。

1*DDl3U145ns-peMbMagQU8A.png
廣播可以通過記錄每個連接的客戶端輕松完成,假設(shè)我們需要更新游戲的排行榜,服務(wù)器可以將相同的消息發(fā)送給每個連接的客戶端。

1*RJALC3NM0ZvRADUFFfFAHQ.png
SocketIO
SocketIO[1] 是一個事件驅(qū)動的JavaScript庫,可在服務(wù)器和客戶端中使用,以更高效地處理WebSocket。它包括自動重新連接和故障回退機制,如果握手失敗,將提供長輪詢連接作為實時連接。此外,它提供了命名空間和房間廣播事件,您可以僅將消息發(fā)送到特定的通道。
WebRTC
WebRTC是一種基于UDP的技術(shù),提供點對點通信。該協(xié)議的主要優(yōu)點是它在對等方之間具有非常低的延遲。信息是從客戶端傳輸?shù)娇蛻舳?,無需中央服務(wù)器,并且使用UDP協(xié)議作為傳輸層使連接速度非???。它通常用于涉及實時媒體通信的應(yīng)用程序,其中丟失一些數(shù)據(jù)包并不是大問題。
默認情況下,WebRTC提供端到端加密,使連接在通過互聯(lián)網(wǎng)傳輸時保持安全。
信令
此過程用于對等方之間交換其連接。為了實現(xiàn)這一點,需要一個具有已連接對等方信息的服務(wù)器來建立此連接。
要實現(xiàn)這一點,一個對等方必須發(fā)送一個帶有其會話描述協(xié)議(SDP)的提議,其中包括有關(guān)客戶端的關(guān)鍵信息,例如要接收的內(nèi)容(例如視頻、音頻、兩者兼有)、瀏覽器支持的選項、編解碼器等。您要連接的對等方將接收此提議請求并存儲此會話描述,并創(chuàng)建一個答案。
當另一個對等方接收到響應(yīng)時,它將存儲到達的會話描述作為遠程描述。在完成此操作后,兩個對等方可以使用已建立的流相互連接。兩個對等方將交換其ICE候選項,其中包含兩者都需要通過互聯(lián)網(wǎng)連接所需的信息(IP地址、端口等)。一旦完成,連接應(yīng)該正常運行,對等方可以交換媒體。
STUN/TURN服務(wù)器
這個方案的主要困難在于連接在防火墻后工作,因此連接將被拒絕。為了克服這個問題,STUN服務(wù)器將幫助我們獲取對等方的IP或?qū)Φ确降腎P。因此,在ICE候選項中,將設(shè)置STUN的IP和端口,以便對等方將與服務(wù)器通信,然后代理到客戶端。
在大多數(shù)情況下,此配置將足夠,但在某些情況下,對等方的安全性較高,STUN服務(wù)器將無法解開其他對等方的地址。這就是TURN服務(wù)器的出現(xiàn),它是連接的對等方之間的中介對等方。對等方將媒體發(fā)送到此服務(wù)器,它將能夠?qū)⑵浒l(fā)送回其他對等方。

1*1sogm-7H_BSRWhlRPdxyVA.png
應(yīng)用
如上所述,此技術(shù)在嘗試向一個或多個對等方傳輸媒體(如音頻、視頻或兩者)時產(chǎn)生差異。這就是為什么應(yīng)用程序如Google Meet、Discord、Twitch使用它作為其實時通信機制的原因,使連接在每個連接的對等方之間保持安全且快速。使用WebRTC進行廣播非常容易實現(xiàn),延遲非常低,而使用其他技術(shù),如WebSockets,連接將存在較大的延遲問題,因為所有傳輸?shù)拿襟w都會通過中央服務(wù)器發(fā)送,然后通過TCP將數(shù)據(jù)包發(fā)送回其他客戶端,使過程更加安全但更慢。
文件共享是WebRTC的強項,應(yīng)用程序如WebTorrent使用它在瀏覽器中傳輸點對點文件,使用BitTorrent協(xié)議。
盡管最初是為Web瀏覽器開發(fā)的,但使用此技術(shù)設(shè)計了許多非瀏覽器設(shè)備的應(yīng)用程序,包括移動平臺和物聯(lián)網(wǎng)設(shè)備。
可擴展消息和出席協(xié)議(Jabber)
這是一種基于TCP的分散式協(xié)議,允許在網(wǎng)絡(luò)上傳輸XML元素以實現(xiàn)近實時的消息和出席信息交換。
它基于客戶端-服務(wù)器架構(gòu),其中一個客戶端將其信息,如出席或消息,通過服務(wù)器發(fā)送給另一個客戶端。如果接收方未連接到與發(fā)送方相同的服務(wù)器,則服務(wù)器將與其他XMPP服務(wù)器通信,直到找到客戶端。這就是為什么這種架構(gòu)是分散式的,不是每個客戶端都連接到同一個中央服務(wù)器,客戶端和服務(wù)器都是相互連接的。

1*qY34YQaFqusEchQIutoFGQ.png
XML流從一個客戶端發(fā)送到另一個客戶端,使用JID(Jabber標識),每個客戶端都有一個具有以下結(jié)構(gòu)的唯一標識符:

1*gFKdZ9U1XWcw6h2FQP2Acw.png
- 本地部分:與電子郵件中“@”之前的部分完全相同,通常在此處使用客戶端的名稱。
- 域部分:它是指連接此客戶端的服務(wù)器。
- 資源部分:顧名思義,用于指定要用于將消息發(fā)送到服務(wù)器的資源。例如,同一服務(wù)器可以處理來自移動應(yīng)用程序、Web、桌面應(yīng)用程序等的消息。
XMPP Stanza
XMPP stanza是從客戶端之間發(fā)送的XML元素,充當了從客戶端之間發(fā)送的結(jié)構(gòu)化信息的基本單位。有三種主要的stanza:
(1)消息:
- 客戶端到客戶端
- 發(fā)送并忘記
- 無需確認
- 對于不需要響應(yīng)的任何內(nèi)容(聊天、警報、日志記錄等)都很有用。
Hey, how you doin'? pNltztLMBQhqakHwcFd(2)信息查詢:
- 一對一
- 確認
- 至少一次的可選交付
<iq id="30" type="result"
from="user-two@server.org/mobile"
to="user-one@server.org/desktop" />(3)出席:
- 定向(一對一)或廣播(一對多)
- 在網(wǎng)絡(luò)上宣布實體的可用性
Studying away輪詢和長輪詢
在創(chuàng)建這些協(xié)議和技術(shù)之前,開發(fā)人員創(chuàng)建了幾種機制和策略,以實現(xiàn)類似實時通信的結(jié)果。在仍在使用的最著名的機制中,我想強調(diào)這兩種:輪詢:這種機制背后的想法是每隔x秒向服務(wù)器發(fā)送一個請求,以檢查是否有新數(shù)據(jù)到達。
// 每一秒調(diào)用一次fetch以獲取新消息
setInterval(1000, () => {
fetch('http://my-awesome.server.com/new-messages')
.then(response => response.json())
.then(data => updateInterface(data));
})長輪詢:為了克服前述問題,客戶端將發(fā)送請求以檢查新數(shù)據(jù)。服務(wù)器將保持請求保持打開,直到新數(shù)據(jù)可用。一旦可用,服務(wù)器會響應(yīng)并發(fā)送新信息。客戶端收到新信息后,立即發(fā)送另一個請求,然后重復(fù)操作。
// 此函數(shù)將持續(xù)從服務(wù)器輪詢數(shù)據(jù)
// 它可能等待響應(yīng)1毫秒,也可能等待1小時
async function getNewData() {
const data = await fetch('http://my-awesome.server.com/new-messages');
updateInterface(data.json());
getNewData();
}
getNewData();這兩種策略實現(xiàn)了與RTC協(xié)議非常相似的結(jié)果,但性能問題較大,可能會通過阻塞事件循環(huán)[2]來導(dǎo)致屏幕凍結(jié)。
























