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