實(shí)時(shí)通信協(xié)議
本文旨在簡(jiǎn)要解釋如何在Web上實(shí)現(xiàn)客戶端/服務(wù)器和客戶端/客戶端之間的實(shí)時(shí)通信,以及它們的內(nèi)部工作原理和最常見(jiàn)的用例。
TCP vs UDP
TCP和UDP都位于OSI模型的傳輸層,負(fù)責(zé)在網(wǎng)絡(luò)上傳輸數(shù)據(jù)包。它們之間的主要區(qū)別在于,TCP在傳輸數(shù)據(jù)之前會(huì)打開(kāi)一個(gè)專用連接,并確保所有數(shù)據(jù)包都到達(dá)目的地,而UDP則不會(huì)。這使得TCP連接速度較慢,但同時(shí)更可靠,因?yàn)樗_保數(shù)據(jù)的到達(dá),而UDP可能更快,但在傳輸過(guò)程中可能會(huì)丟失一些數(shù)據(jù)包。
這個(gè)概念在選擇實(shí)時(shí)通信技術(shù)時(shí)需要牢記,因?yàn)樗鼈兛赡苁褂肨CP或UDP作為傳輸層協(xié)議,具有各自的優(yōu)勢(shì)和劣勢(shì)。例如,如果您正在開(kāi)發(fā)一個(gè)視頻會(huì)議平臺(tái),用戶更希望彼此之間的交互更快,而丟失一些數(shù)據(jù)包是可以接受的。
WebSockets
WebSocket是一種HTTP升級(jí)技術(shù),它提供了基于TCP的持久全雙工、雙向連接。它被設(shè)計(jì)為客戶端/服務(wù)器連接,允許它們隨時(shí)相互發(fā)送數(shù)據(jù)。
1*1CUCOOXEIhcDzBWMDDQb6g.png
握手
為了建立WebSocket連接,客戶端必須向服務(wù)器發(fā)送一個(gè)HTTP 握手 請(qǐng)求以切換協(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)來(lái)回應(yīng),如果不滿足要求,則返回HTTP錯(cuò)誤:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
請(qǐng)注意,客戶端請(qǐng)求包含一個(gè)帶有Base64編碼的隨機(jī)字節(jié)值的 Sec-WebSocket-Key,而服務(wù)器則回復(fù)一個(gè)帶有請(qǐng)求密鑰的哈希值的 Sec-WebSocket-Accept 頭。這是為了防止緩存代理重新發(fā)送先前的WebSocket連接。
實(shí)現(xiàn)
一旦握手被接受,客戶端已切換協(xié)議,客戶端可以向服務(wù)器發(fā)送數(shù)據(jù),服務(wù)器也可以向客戶端發(fā)送數(shù)據(jù)。
在客戶端中,使用瀏覽器的本機(jī)JavaScript引擎,由服務(wù)器發(fā)送的傳入數(shù)據(jù)將由事件函數(shù)處理,一旦數(shù)據(jù)到達(dá),這些事件函數(shù)將被觸發(fā)。
在服務(wù)器端,在大多數(shù)情況下,這也是處理客戶端發(fā)送的數(shù)據(jù)的方式,但也可以通過(guò)永不結(jié)束的循環(huán)來(lái)處理數(shù)據(jù)。
// HTTP握手
const socket = new WebSocket('ws://my-awesome.server.com');
socket.onopen = (event) => {
// 連接建立時(shí)觸發(fā)
socket.send('客戶端發(fā)送到服務(wù)器的數(shù)據(jù)');
});
socket.onmessage = (event) => {
// 服務(wù)器發(fā)送數(shù)據(jù)時(shí)觸發(fā)
console.log(event.data);
});
socket.onclose = (event) => {
// WebSocket連接關(guān)閉時(shí)觸發(fā)
console.log('連接關(guān)閉')
});
預(yù)期使用案例
當(dāng)您需要低延遲的實(shí)時(shí)連接時(shí),WebSocket是一種非常強(qiáng)大的協(xié)議,基于Web的游戲、聊天應(yīng)用程序是可以使用此技術(shù)的很好的示例,因?yàn)樵诳蛻舳酥g進(jìn)行通信非常簡(jiǎn)單:客戶端A可以通過(guò)服務(wù)器向客戶端B發(fā)送消息。
1*DDl3U145ns-peMbMagQU8A.png
廣播可以通過(guò)記錄每個(gè)連接的客戶端輕松完成,假設(shè)我們需要更新游戲的排行榜,服務(wù)器可以將相同的消息發(fā)送給每個(gè)連接的客戶端。
1*RJALC3NM0ZvRADUFFfFAHQ.png
SocketIO
SocketIO[1] 是一個(gè)事件驅(qū)動(dòng)的JavaScript庫(kù),可在服務(wù)器和客戶端中使用,以更高效地處理WebSocket。它包括自動(dòng)重新連接和故障回退機(jī)制,如果握手失敗,將提供長(zhǎng)輪詢連接作為實(shí)時(shí)連接。此外,它提供了命名空間和房間廣播事件,您可以僅將消息發(fā)送到特定的通道。
WebRTC
WebRTC是一種基于UDP的技術(shù),提供點(diǎn)對(duì)點(diǎn)通信。該協(xié)議的主要優(yōu)點(diǎn)是它在對(duì)等方之間具有非常低的延遲。信息是從客戶端傳輸?shù)娇蛻舳?,無(wú)需中央服務(wù)器,并且使用UDP協(xié)議作為傳輸層使連接速度非???。它通常用于涉及實(shí)時(shí)媒體通信的應(yīng)用程序,其中丟失一些數(shù)據(jù)包并不是大問(wèn)題。
默認(rèn)情況下,WebRTC提供端到端加密,使連接在通過(guò)互聯(lián)網(wǎng)傳輸時(shí)保持安全。
信令
此過(guò)程用于對(duì)等方之間交換其連接。為了實(shí)現(xiàn)這一點(diǎn),需要一個(gè)具有已連接對(duì)等方信息的服務(wù)器來(lái)建立此連接。
要實(shí)現(xiàn)這一點(diǎn),一個(gè)對(duì)等方必須發(fā)送一個(gè)帶有其會(huì)話描述協(xié)議(SDP)的提議,其中包括有關(guān)客戶端的關(guān)鍵信息,例如要接收的內(nèi)容(例如視頻、音頻、兩者兼有)、瀏覽器支持的選項(xiàng)、編解碼器等。您要連接的對(duì)等方將接收此提議請(qǐng)求并存儲(chǔ)此會(huì)話描述,并創(chuàng)建一個(gè)答案。
當(dāng)另一個(gè)對(duì)等方接收到響應(yīng)時(shí),它將存儲(chǔ)到達(dá)的會(huì)話描述作為遠(yuǎn)程描述。在完成此操作后,兩個(gè)對(duì)等方可以使用已建立的流相互連接。兩個(gè)對(duì)等方將交換其ICE候選項(xiàng),其中包含兩者都需要通過(guò)互聯(lián)網(wǎng)連接所需的信息(IP地址、端口等)。一旦完成,連接應(yīng)該正常運(yùn)行,對(duì)等方可以交換媒體。
STUN/TURN服務(wù)器
這個(gè)方案的主要困難在于連接在防火墻后工作,因此連接將被拒絕。為了克服這個(gè)問(wèn)題,STUN服務(wù)器將幫助我們獲取對(duì)等方的IP或?qū)Φ确降腎P。因此,在ICE候選項(xiàng)中,將設(shè)置STUN的IP和端口,以便對(duì)等方將與服務(wù)器通信,然后代理到客戶端。
在大多數(shù)情況下,此配置將足夠,但在某些情況下,對(duì)等方的安全性較高,STUN服務(wù)器將無(wú)法解開(kāi)其他對(duì)等方的地址。這就是TURN服務(wù)器的出現(xiàn),它是連接的對(duì)等方之間的中介對(duì)等方。對(duì)等方將媒體發(fā)送到此服務(wù)器,它將能夠?qū)⑵浒l(fā)送回其他對(duì)等方。
1*1sogm-7H_BSRWhlRPdxyVA.png
應(yīng)用
如上所述,此技術(shù)在嘗試向一個(gè)或多個(gè)對(duì)等方傳輸媒體(如音頻、視頻或兩者)時(shí)產(chǎn)生差異。這就是為什么應(yīng)用程序如Google Meet、Discord、Twitch使用它作為其實(shí)時(shí)通信機(jī)制的原因,使連接在每個(gè)連接的對(duì)等方之間保持安全且快速。使用WebRTC進(jìn)行廣播非常容易實(shí)現(xiàn),延遲非常低,而使用其他技術(shù),如WebSockets,連接將存在較大的延遲問(wèn)題,因?yàn)樗袀鬏數(shù)拿襟w都會(huì)通過(guò)中央服務(wù)器發(fā)送,然后通過(guò)TCP將數(shù)據(jù)包發(fā)送回其他客戶端,使過(guò)程更加安全但更慢。
文件共享是WebRTC的強(qiáng)項(xiàng),應(yīng)用程序如WebTorrent使用它在瀏覽器中傳輸點(diǎn)對(duì)點(diǎn)文件,使用BitTorrent協(xié)議。
盡管最初是為Web瀏覽器開(kāi)發(fā)的,但使用此技術(shù)設(shè)計(jì)了許多非瀏覽器設(shè)備的應(yīng)用程序,包括移動(dòng)平臺(tái)和物聯(lián)網(wǎng)設(shè)備。
可擴(kuò)展消息和出席協(xié)議(Jabber)
這是一種基于TCP的分散式協(xié)議,允許在網(wǎng)絡(luò)上傳輸XML元素以實(shí)現(xiàn)近實(shí)時(shí)的消息和出席信息交換。
它基于客戶端-服務(wù)器架構(gòu),其中一個(gè)客戶端將其信息,如出席或消息,通過(guò)服務(wù)器發(fā)送給另一個(gè)客戶端。如果接收方未連接到與發(fā)送方相同的服務(wù)器,則服務(wù)器將與其他XMPP服務(wù)器通信,直到找到客戶端。這就是為什么這種架構(gòu)是分散式的,不是每個(gè)客戶端都連接到同一個(gè)中央服務(wù)器,客戶端和服務(wù)器都是相互連接的。
1*qY34YQaFqusEchQIutoFGQ.png
XML流從一個(gè)客戶端發(fā)送到另一個(gè)客戶端,使用JID(Jabber標(biāo)識(shí)),每個(gè)客戶端都有一個(gè)具有以下結(jié)構(gòu)的唯一標(biāo)識(shí)符:
1*gFKdZ9U1XWcw6h2FQP2Acw.png
- 本地部分:與電子郵件中“@”之前的部分完全相同,通常在此處使用客戶端的名稱。
- 域部分:它是指連接此客戶端的服務(wù)器。
- 資源部分:顧名思義,用于指定要用于將消息發(fā)送到服務(wù)器的資源。例如,同一服務(wù)器可以處理來(lái)自移動(dòng)應(yīng)用程序、Web、桌面應(yīng)用程序等的消息。
XMPP Stanza
XMPP stanza是從客戶端之間發(fā)送的XML元素,充當(dāng)了從客戶端之間發(fā)送的結(jié)構(gòu)化信息的基本單位。有三種主要的stanza:
(1)消息:
- 客戶端到客戶端
- 發(fā)送并忘記
- 無(wú)需確認(rèn)
- 對(duì)于不需要響應(yīng)的任何內(nèi)容(聊天、警報(bào)、日志記錄等)都很有用。
Hey, how you doin'? pNltztLMBQhqakHwcFd
(2)信息查詢:
- 一對(duì)一
- 確認(rèn)
- 至少一次的可選交付
<iq id="30" type="result"
from="user-two@server.org/mobile"
to="user-one@server.org/desktop" />
(3)出席:
- 定向(一對(duì)一)或廣播(一對(duì)多)
- 在網(wǎng)絡(luò)上宣布實(shí)體的可用性
Studying away
輪詢和長(zhǎng)輪詢
在創(chuàng)建這些協(xié)議和技術(shù)之前,開(kāi)發(fā)人員創(chuàng)建了幾種機(jī)制和策略,以實(shí)現(xiàn)類似實(shí)時(shí)通信的結(jié)果。在仍在使用的最著名的機(jī)制中,我想強(qiáng)調(diào)這兩種:輪詢:這種機(jī)制背后的想法是每隔x秒向服務(wù)器發(fā)送一個(gè)請(qǐng)求,以檢查是否有新數(shù)據(jù)到達(dá)。
// 每一秒調(diào)用一次fetch以獲取新消息
setInterval(1000, () => {
fetch('http://my-awesome.server.com/new-messages')
.then(response => response.json())
.then(data => updateInterface(data));
})
長(zhǎng)輪詢:為了克服前述問(wèn)題,客戶端將發(fā)送請(qǐng)求以檢查新數(shù)據(jù)。服務(wù)器將保持請(qǐng)求保持打開(kāi),直到新數(shù)據(jù)可用。一旦可用,服務(wù)器會(huì)響應(yīng)并發(fā)送新信息。客戶端收到新信息后,立即發(fā)送另一個(gè)請(qǐng)求,然后重復(fù)操作。
// 此函數(shù)將持續(xù)從服務(wù)器輪詢數(shù)據(jù)
// 它可能等待響應(yīng)1毫秒,也可能等待1小時(shí)
async function getNewData() {
const data = await fetch('http://my-awesome.server.com/new-messages');
updateInterface(data.json());
getNewData();
}
getNewData();
這兩種策略實(shí)現(xiàn)了與RTC協(xié)議非常相似的結(jié)果,但性能問(wèn)題較大,可能會(huì)通過(guò)阻塞事件循環(huán)[2]來(lái)導(dǎo)致屏幕凍結(jié)。