本文旨在简要解释如何在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]来导致屏幕冻结。