HTTP、WebSocket、gRPC 或 WebRTC:各种协议的区别
在为您的应用程序选择通信协议时,有很多不同的选择。 本文将了解四种流行的解决方案:HTTP、WebSocket、gRPC 和 WebRTC。 我们将通过深入学习其背后原理、最佳用途及其优缺点来探索每个协议。
通信方式在不断改进:变得更快、更方便、更可靠,从使用信鸽发送信息,发展到邮政邮件,再到固定电话的发明,再到便携式移动设备。未来,甚至可能将会议和生日派对过渡到 VR(但愿这只是个玩笑!)。
当然,最好的沟通方式总是要视情况而定。快速的短信有时比长的电子邮件更好。 其他时候,与团队进行视频通话是交流信息的最佳方式, 相比之下,重要的保险文件必须通过普通邮件发送并以纸质形式交付。而使用的网络技术和协议也是如此,不同的应用程序有不同的通信需求。
概述
在本文中,我们将介绍一些可以作为开发人员使用的流行通信协议,并探讨不同协议的优缺点。 正如开头所说,没有比另一个更好的解决方案,只有一些解决方案更适合特定的应用程序或问题。
一些应用程序需要点对点连接,具有低延迟和高数据传输,并且可以接受一些数据包(信息)丢失。 有些应用程序可以根据需要轮询服务器,而不需要获取被轮询方的同等数据,有些应用程序需要具有数据可靠性的实时通信,诸如此类。
在线多人游戏、消息传递应用程序、博客网站、媒体库应用程序和视频会议软件都有不同的通信和数据需求。如果您正在构建视频流解决方案,那可能还有其他注意事项。
什么是通信协议?
在计算机网络中,协议是一组规则,用于管理数据在设备之间的交换方式。 该协议定义了通信的规则、语法、语义和同步以及可能的错误恢复方法。本文中讨论的协议定义了应用层软件如何相互交互。 不同的协议遵循不同的规则,了解每个协议的优势和局限性至关重要。
不同协议的时间线
在本文中,您将了解以下协议:
- HTTP(超文本传输协议):是分布式协作和超媒体信息系统的应用协议。 HTTP 是万维网数据通信的基础, 超文本是在包含文本的节点之间使用逻辑链接(超链接)的结构化文本。 HTTP 是交换或传输超文本的协议。
- HTTP/2: 旨在解决原始 HTTP 协议的缺点并提高性能。 HTTP/2 比 HTTP/1.1 更快、更高效,它支持多路复用,允许多个请求和响应在单个连接上进行多路复用。 其他值得注意的功能包括标头压缩和服务器推送, 它正逐渐成为网络流量的默认协议。
- WebSocket: 是一种允许客户端和服务器之间进行双向通信的协议。 它是处理实时数据应用程序的热门选择,例如聊天应用程序、在线游戏和实时数据流。
- gRPC :是一个使用 HTTP/2 进行传输的现代开源 RPC 框架。 对于需要进行大量小而快的 API 调用的应用程序来说,这是一个很好的选择。 gRPC 为多种语言生成跨平台的客户端和服务器绑定,使客户端应用程序可以直接调用不同机器上的服务器应用程序的方法,就好像它是本地方法一样。
- WebRTC: 是一种允许客户端之间进行实时通信,并使得建立直接对等连接成为可能的技术。 它用于视频、聊天、文件共享和实时视频流应用程序。
了解 TCP 和 UDP
在深入研究上述应用层之前,重要的是要对 TCP 和 UDP 有一个基本的了解,这两个底层传输层以根本不同的方式促进数据传输。
- TCP(传输控制协议):是定义如何通过 Internet 建立和维护网络对话的标准。 TCP 是 Internet 和任何面向连接的网络上最常用的协议。 当您浏览网页时,您的计算机会向网络服务器发送 TCP 数据包。 Web 服务器通过将 TCP 数据包发送回您的计算机来响应。 在交换任何数据之前,首先在两个设备之间建立连接,并且 TCP 使用纠错来确保所有数据包都成功传递。 如果数据包丢失或损坏,TCP 将尝试重新发送它。
- UDP(用户数据报协议):是一种无连接、不可靠的传输层协议。 它不需要建立或维护连接,也不保证消息将按顺序传递。 这意味着如果数据包未发送或损坏,可能会丢失一些数据。 UDP 通常用于流媒体或实时应用程序,在这些应用程序中,丢失数据包的问题比确保交付要少。
HTTP/1
对应用层所有基于互联网的通信和数据传输的基础——HTTP(超文本传输协议)有一个基本的了解是很重要的。在更详细地探索其他协议并充分理解它们提供的功能之前,了解 HTTP/1 及其局限性也很重要。
使用 HTTP,客户端和服务器通过交换单独的消息进行通信。 客户端发送的消息称为请求,服务器发送的消息称为响应。 这些消息作为常规文本消息通过 TCP 连接发送。 它们也可以使用 TLS 加密并使用 HTTPS 协议发送。
客户端通常是在用户手机或计算机上运行的 Web 浏览器或应用程序,但从技术上讲,它可以是任何东西,例如,抓取网站的脚本。HTTP 请求只能沿一个方向流动,即从客户端到服务器。 服务器无法发起与客户端的通信, 它只能响应请求。
HTTP/1.1传输示例
HTTP 非常适合传统的 Web 和客户端应用程序,在这些应用程序中,信息是按需获取的。 例如,你刷新了一个页面,向服务器发出了获取最新信息的请求。
在接下来的部分中,我们将探讨 HTTP/1 的一些限制。
– HTTP/1 实时传输
当消息需要从客户端实时发送到服务器时,HTTP/1 效率低下。 例如,如果服务器上有新信息需要与客户端共享,则此事务只能在客户端发起请求后发生。当然,也有一些解决方法,例如使用称为 HTTP 短轮询和长轮询以及服务器发送事件的技术。
短轮询
HTTP 短轮询是一种客户端重复向服务器发送请求直到它响应新数据的技术。 一旦它接收到数据,它就会再次启动该过程并反复询问,直到有其他可用的数据为止。
HTTP短轮训
这是一种低效的实时通信策略,因为它通过持续传输和解析 HTTP 请求/响应浪费了大量资源。
长轮询
使用 HTTP 长轮询,从客户端发出单个请求,然后服务器保持该连接打开,直到有新数据可用并且可以发送响应。 客户端收到响应后,立即再次建立新的连接。
HTTP长轮询
长轮询比短轮询更有效,但不是实时通信的最佳解决方案。
服务器发送的事件 (SSE)
服务器发送的事件允许客户端保持打开的连接并实时从服务器接收推送消息,而不必不断地轮询服务器以获取新数据。 这是一种单向连接,因此您无法将事件从客户端发送到服务器。
SSE 是一种标准,描述了一旦建立了初始客户端连接,服务器如何启动向客户端的数据传输。
– HTTP/1 的性能问题
大多数 HTTP 数据流由小的、密集的数据传输组成,而 TCP 针对长期连接和批量数据传输进行了优化。 在大多数情况下,网络往返时间是 TCP 吞吐量和性能的限制因素。 因此,延迟是性能瓶颈,大多数 Web 应用程序都可以克服它。
上面的意思是 HTTP 使用的 TCP 是为处理长期连接和传输大量数据而构建的。 另一方面,HTTP/1 会打开一堆短暂的 TCP 连接,并且通常只发送小块数据。
队首阻塞
HTTP/1.0 的一个性能问题是您必须为每个请求/响应打开一个新的 TCP 连接。 对于 HTTP 最初发明的目的而言(获取超文本文档),这不是问题。 文档部分很重要,因为 HTTP 并不意味着“超媒体”。
随着网络的发展,为每个请求打开一个新的 TCP 连接成为一个问题。 我们开始构建完整的应用程序而不是简单的网页,浏览器需要检索的媒体和文件的数量变得更多。 设想一个典型的 Web 应用程序需要 HTML、CSS 和 JavaScript 文件,以及各种图像和其他资产。 对于每个文件,都必须建立一个新的连接。
随之而来的是 HTTP/1.1,它具有持久连接,它定义了可以在同一个 TCP 连接上有多个请求或响应。这个解决方案绝对是一个改进,但它不允许服务器同时返回多个响应。 这是一个序列化协议,您必须在其中发送请求并等待响应,然后发送第二个请求,依此类推。 这称为队头阻塞。
然而,实现某种并行性是可能的,因为浏览器最多可以打开六个(不同浏览器有差异)同一来源的 TCP 连接(其中“来源”被定义为主机和端口号的唯一组合)。
例如,如果您有一个需要加载 12 张图像的照片库应用程序,那么将发出六个请求来加载前六张图像,并且每个请求都会在后台打开一个新的 TCP 连接。 其他六个图像将被阻止,直到收到响应并且打开的连接之一可用于加载下一个图像。 最初的六个开放的同源 TCP 连接将在可用时被重用,但您仅限于六个活动连接。
很自然地,程序员找到了一个简单的解决方法——改变图片源。 不是在同一个源上托管所有资产,而是在一个源上托管六个图像,其余的在另一个源上。 现在您可以有 12 个并发请求(或打开 TCP 连接),这被称为“分片”。
- 图片 1–6 托管在 1.images.com 上
- 图片 7–12 托管在 2.images.com 上
但是,您可以执行此操作的次数是有限制的,而且很难确定最佳的分片数量。 在某些时候,添加更多分片会增加复杂性、增加开销,并可能导致链路拥塞和数据包丢失。
还有其他问题,因为每个 TCP 连接都会给服务器增加不必要的开销。 连接相互竞争,每次 TCP 和 TLS 握手都会增加不必要的成本,并且必须使用其他服务器/代理资源来维持活动连接。 HTTP/1 使用底层 TCP 连接的方式有一个明显的限制。
标头(Headers)和 Cookie 爆炸
另一个问题是,随着 HTTP 规范的发展,规范中添加了更多的标头。 开发人员还可以选择将 cookie 添加到标头,这些 cookie 可以任意大。 这增加了很多大小,因为每个请求和响应都需要传输所有这些文本信息,并且 HTTP/1.1 不包含压缩标头和元数据的机制。
如果您需要一个高性能的 RPC 协议,这种开销会变得更大,此时 HTTP 不再是最佳解决方案。
优先级(Prioritization)
在 HTTP/1.1 中,浏览器通过在客户端持有一个优先级队列来“确定”资源的优先级,并对如何充分利用可用的 TCP 连接进行有根据的猜测。 浏览器嵌入了启发式方法,用于确定哪些资源比其他资源更有价值。
例如,加载 CSS 将比加载图像具有更高的优先级。问题在于,作为开发人员,您无法确定一个请求的优先级高于另一个请求或更改正在进行的消息的优先级。 首先加载什么内容取决于浏览器,您对优先级方案没有发言权。
HTTP/2
HTTP/2 的优点
HTTP/2 是 HTTP 协议的改进版本,解决了上述 HTTP/1 的所有性能问题,并在不改变任何语义(标头等)的情况下添加了其他增强功能。
HTTP/2 中最显着的变化是使用多路复用。通过单个 TCP 连接同时发送和接收多个 HTTP 请求和响应。 所有 HTTP/2 连接都是持久的,每个源只需要一个连接。 这允许更有效地使用网络资源,并可以显着提高应用程序的性能。
HTTP/2 的其他一些好处:
- 使用标头压缩来减小标头的大小,从而避免一遍又一遍地发送相同的纯文本标头。 这显着减少了请求/响应的开销和发送的数据量。
- 启用优先级,允许客户端(开发人员)指定其所需资源的优先级。 也可以更新正在进行的请求的优先级——例如,在滚动时,如果图像不再可见,则优先级可以改变。
- 使用服务器推送,在客户端请求之前将数据发送到客户端。 这可用于通过消除客户端发出多个请求的需要来缩短加载时间。
HTTP/2 是如何工作的?
HTTP/2 中的基本协议单元是帧。 这种新的二进制帧机制改变了客户端和服务器之间的数据交换方式。
该标准定义了十种不同的帧类型,每种都有不同的用途。 例如,HEADERS 和 DATA 帧构成了 HTTP 请求和响应的基础:
HTTP/1.1 vs HTTP/2
帧是承载特定类型数据的最小通信单位。 其他一些帧示例是:
- 设置(SETTINGS):在开始或连接期间交换设置信息。
- 优先级(PRIORITY):重新分配消息的优先级。
- PUSH_PROMISE:允许服务器向您推送数据,这是对服务器将发送的内容的承诺。 例如,如果您请求 index.html,服务器可以创建一个 PUSH_PROMISE 来承诺推送 app.js 和 styles.css,这意味着客户端不需要主动请求这些资源。
帧组合在一起形成消息,例如上图中的标题和数据帧。 这等同于正常的请求或响应。最后,一系列消息可以成为流的一部分。 这允许客户端和服务器之间的双向数据流以及完整的请求和响应多路复用。
多路复用
上图有点误导,给人的印象是客户端和服务器之间打开了多个连接。但它是单个 TCP 连接,数据以非阻塞方式在客户端和服务器之间自由流动。
多路复用
新的二进制框架层允许客户端和服务器将 HTTP 消息分解为独立的帧,组合发送,然后在另一端重新组合它们。这只是 HTTP/2.0 工作原理的总结。 如果您想了解更多信息并探索优先级排序、服务器推送和标头压缩,请参阅文末资料。
HTTP/2 双向数据流
HTTP/2 规范表明:
“流”是在 HTTP/2 连接中客户端和服务器之间交换的独立地、双向帧序列。
流有几个重要的特征:
- 单个 HTTP/2 连接可以包含多个并发打开的流,其中任一端点交错来自多个流的帧。
- 流可以单方面建立和使用,也可以由客户端或服务器共享。
- 流可以被任一端点关闭。
服务器推送功能存在很多误解,它允许服务器通过 HTTP/2 主动发送它认为您可能需要的资源,例如 .js 和 .css 文件,而无需客户端请求。 这与双向流无关,只是一种针对可缓存资源的 Web 优化技术。
事实是,对于 HTTP/2 服务器无法启动流。 但是一旦客户端通过发送请求打开一个流,双方就可以随时通过持久套接字发送数据帧。 一个很好的例子是 gRPC,我们将在后面讨论。
使用 HTTP/2,可以实现双向数据流,你可以争辩说它是比 WebSockets 之类的东西更优化的解决方案,或者你可以争辩说它不是。 我们将在 WebSocket 部分更详细地讨论这个问题。
WebSockets
WebSockets 规范说明
来自 WebSocket 协议规范:
该技术的目标是为基于浏览器的应用程序提供一种机制,这些应用程序需要与不依赖于打开多个 HTTP 连接的服务器进行双向通信(例如,使用 XMLHttpRequest 或 iframe 和长轮询)。
WebSockets 的发明是为了实现客户端和服务器之间的全双工通信,这允许数据立即通过单个打开的连接双向传输。
建立 WebSocket 连接后,客户端无需轮询服务器以获取更新。 相反,通信是双向发生的。 与 HTTP/1 的原始长轮询和短轮询相比,这提高了速度和实时能力。 WebSocket 没有特定要遵循的数据格式, 您可以使用它发送任何数据、文本或字节,这种灵活性是 WebSockets 流行的原因之一。
其中一些内容听起来可能与我们在 HTTP/2 部分中讨论的内容很熟悉,但重要的是要注意 WebSockets 是在 HTTP/2 之前很久就发明的。 稍后我们将对它们进行更多比较。
WebSockets 如何工作?
WebSockets 底层基于 TCP 的传输层运行。 要建立 WebSocket 连接,客户端和服务器首先需要通过正常的 HTTP/1.1 连接执行握手。此握手是从 HTTP 升级到 WebSockets 的桥梁。
下面是一个示例客户端握手请求。客户端可以使用称为升级标头的 HTTP/1.1 机制将其连接从 HTTP 切换到 WebSockets:
GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
然后服务器将用一个特殊的响应结束握手,该响应表明协议将从 HTTP 更改为 WebSocket:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
WebSockets 需要具有 ws:// 或 wss:// 方案的统一资源标识符 (URI)。 ws:// 方案用于未加密连接,wss:// 方案用于加密连接,类似于 HTTP URL 使用 http:// 或 https:// 方案的方式。
一旦建立了双向通信通道,客户端和服务器就可以来回发送消息。 这些消息可以是从二进制数据到文本的任何内容。 WebSocket 连接将保持打开状态,直到客户端或服务器断开连接。
WebSocket 多路复用
在撰写本文时,WebSocket 协议不支持内置多路复用。 我们在 HTTP/2 部分讨论了多路复用,了解到它是 HTTP/2 的内置功能,并且可以在同一连接上多路复用多个流。 每个流都有一个唯一的标识符,所有发送的帧都有一个与相应流关联的 ID。
不支持多路复用意味着 WebSocket 协议需要为每个 WebSocket 连接建立一个新的传输连接。 例如,在同一浏览器的不同选项卡中运行的多个客户端将使用单独的连接。 要在 WebSockets 上进行多路复用,您通常需要依赖第三方插件或库。
WebSocket 与 HTTP/2
HTTP/2 取代了 WebSockets 了吗? 答案是不!
HTTP/2 使双向流成为可能,这使得 WebSockets 不是唯一/最佳选择。 与 WebSockets 相比,HTTP/2 作为规范为您做了更多的工作。 它具有内置的多路复用功能,在大多数情况下,将导致与源站建立的 TCP 连接更少。 另一方面,WebSockets 提供了很大的自由度,并且在建立连接后如何在客户端和服务器之间发送数据方面没有限制。 但是,您需要自己管理重新连接(或依赖为您执行此操作的库)。
至于那个更好,本文不给出答案,每个人有每个人的看法。WebSockets 提供了很大的灵活性,并且作为一个既定标准,它得到所有现代浏览器的完全支持,并且围绕客户端和服务器库的生态系统是强大的。有关更详细和自以为是的讨论,请参阅这些 Stack Overflow 问题:
- HTTP/2 是否让 WebSocket 过时了
- 用于低延迟客户端到服务器消息的 HTTP/2 或 Websockets
- 用于双向消息流的 HTTP/2 与网络套接字
还有一个 RFC 允许在 HTTP/2 连接的单个流上运行 WebSocket 协议的机制。能够从 HTTP/2 引导 WebSockets ,即允许两个协议共享一个 TCP 连接,并将 HTTP/2 对网络的更有效使用扩展到 WebSockets。这已在 Chrome 和 Firefox 中实现。 您可以在此处阅读 Chrome 设计文档和动机。
什么时候使用 WebSocket?
Websockets 最适合需要实时双向通信的应用程序,以及需要快速传输小块数据的应用程序:
- 聊天应用
- 多人游戏
- 协作编辑应用程序
- 直播体育赛事
- 股票交易应用
- 实时活动提要
对于服务器发送事件 (SSE), 在 HTTP/2 上非常高效且易于使用。 SSE 不是双向通信系统; 服务器单方面向客户端推送数据。 但是,如果您所需要的只是服务器向客户端发送数据的一种方式,那么这可能是比使用 WebSockets 更好的选择。 当 HTTP/2 不可用时,SSE 也会回退到 HTTP/1.1。 此外,客户端(或浏览器)为您管理连接并支持自动重新连接。
如果通过 WebSockets 的连接丢失,则不包含用于负载平衡或重新连接的机制。 这必须手动或由第三方库实现。
gRPC
gRPC 是一个现代开源高性能远程过程调用 (RPC) 框架,可以在任何环境中运行。 它可以通过对负载平衡、跟踪、健康检查和身份验证的可插拔支持,有效地连接数据中心内和跨数据中心的服务。 它还适用于分布式计算的最后一英里,将设备、移动应用程序和浏览器连接到后端服务。
gRPC 是一个开源的、基于约定的 RPC 系统,最初由谷歌开发。 gRPC 使应用程序能够透明地通信并简化连接系统的构建。它为多种语言生成跨平台的客户端和服务器绑定,使客户端应用程序可以直接调用不同机器上服务器应用程序的方法,就好像它是本地对象一样。
gRPC 基于 HTTP/2 构建,利用双向流和内置传输层安全性 (TLS) 等功能。
gRPC 动机
深入了解 gRPC 背后的动机、发明它的原因以了解其好处非常重要。 为什么不使用我们已经拥有的现有技术:例如 HTTP/2 和 WebSockets? 为什么我们需要在已有的能力之上再抽象一层?
可以通过多种方式构建数据并通过 Internet 发送数据。 一些流行的例子是 SOAP、REST 和 GraphQL。 您甚至可以创建自己的协议,通过原始 TCP 发送数据,并根据需要自行处理实现。
但无论您选择什么作为您的通信协议,问题在于您需要确保客户端和服务器就该协议达成一致。 例如,如果您正在构建 REST API,则需要用于发送 REST 数据的客户端库是 HTTP 库。 HTTP 库默认内置于浏览器中,浏览器会为您处理一切:
- 它与服务器建立通信。
- 它处理 HTTP/2 并回退到 HTTP/1, 并且将来需要支持 HTTP/3。
- 它处理 TLS 并协商协议。
- 它处理标头、流和其他所有内容。
但是,如果您不在浏览器上怎么办? 如果您是在某个服务器上运行的 Python 应用程序、GoLang CLI 或在 iOS 上运行的 Flutter 应用程序怎么办? 所有这些客户端都需要自己的 HTTP 库,该库可以理解您正在与之通信的协议。
幸运的是,许多人正在为所有这些语言和框架开发各种 HTTP 库。 有些语言甚至有多个具有不同特性的 HTTP 库。 然而,所有这一切都是有代价的,那就是维护。
如果您要将服务器升级到 HTTP/2(如果您使用的 GoLang 库支持它),此成本可能会影响您。 但是,在您的前端 Python 客户端上,等效的 HTTP 库尚未实现 HTTP/2,或者可能不再维护。
随着 HTTP 规范的发展,这些库必须同步更新,如安全问题、新功能等。 HTTP 只是一个例子,对于 WebSocket 协议或任何其他协议也是如此。 有些东西可能在主流浏览器中得到很好的实现,但该功能必须移植到多种不同的语言和框架中。
gRPC 有何不同?
gRPC 试图通过维护流行语言本身的库来解决这个问题,这意味着所有这些语言都将支持添加的新功能。
gRPC 使用 HTTP/2 作为其协议,但是,这个实现对您是无感的。 将来,gRPC 的维护者可以轻松地将 HTTP/2 替换为 HTTP/3,您将立即从该更改中受益。
gRPC 还使用协议缓冲区作为接口定义语言 (IDL) 及其底层消息交换格式。 这种格式是语言中立的,可以轻松地在不同的编程语言之间进行通信。 我们将在下一节中更多地探讨这个概念。
什么是协议缓冲区?
Protocol buffers 是 Google 设计的语言中立、平台中立、可扩展的结构化数据序列化机制——想想 XML,但更小、更快、更简单。 您一次性定义了数据的结构方式。 然后,您可以使用特殊生成的源代码轻松地将结构化数据写入各种数据流并使用各种语言从中读取结构化数据。
在 API 机制的设定中,您通常无需关心协议本身。 例如,如果您使用的是 REST,您通常只是发送带有键/值对的 JSON 消息,直到消息到达接收端才进行检查。 此消息通常可以是任何内容,由您来确保定义了正确的结构。
查看以下 JSON 数据(payload):
{
'id': 123,
'name': 'Gordon',
'email': 'gordon@somewhere.io'
}
在客户端/服务器上接收到此数据,就可以将其反序列化为一个对象,例如:
class Person {
int id;
String name;
String email
}
但是,作为开发人员,您需要为上述数据实现正确的序列化和反序列化逻辑——这可能涉及手动编写 toJson 和 fromJson 方法,可以依赖于代码生成,或者它可能是你正在使用的语言。
无论您如何序列化此数据,底层代码都需要手动更新,可能在多个环境中,以防模式发生变化。而使用协议缓冲区,您可以创建一个模式来定义字段的对象类型并指定哪些是必需的,哪些是可选的:
// The request message containing the person’s information
Message Person {
optional int32 id = 1;
required string name = 2;
optional string email = 3;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
然后您可以指定对外的方法:
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (Person) returns (HelloReply) {}
}
一旦您指定了数据结构和模式,您就可以使用协议缓冲区编译器 将 protoc 从您的 proto 定义中,以您的首选语言生成数据访问类。这些是描述 proto 文件中概述对象的接口,每个字段都有访问器,以及将整个结构序列化/解析为原始字节的方法。
gRPC 模式
gRPC 有四种传输方式, 这四种模式实现了之前讨论的行为,例如,正常的请求/响应、SSE 和 WebSockets。
一元 RPC
一元 RPC 是简单的请求和响应,类似于调用函数。 客户端请求一些数据,服务器进行一些处理并返回该数据。
服务器流式 RPC
服务器流式 RPC,其中客户端向服务器发送单个请求并期望多个响应或响应流。 客户端从返回的流中读取,直到没有更多消息为止。
一个例子是视频流,您请求加载视频,服务器响应视频流。
客户端流式 RPC
客户端流式 RPC,其中客户端写入一系列消息并将它们发送到服务器,再次使用提供的流。 客户端完成消息写入后,它会等待服务器读取消息并返回响应。
一个例子是将一个大文件上传到服务器,一旦所有数据发送完毕,客户端可以发送最终消息以指示上传完成,并且服务器可以选择响应。
双向流 RPC
客户端和服务器流媒体的组合。 聊天应用程序或多人视频游戏是数据需要在客户端和服务器之间自由流动的示例。
双向流式 RPC,双方使用读写流发送一系列消息。 这两个流独立运行,因此客户端和服务器可以按照他们喜欢的任何顺序进行读写。
在双向流式 RPC 中,调用由调用方法的客户端发起。 客户端和服务器端流处理是特定于应用程序的。 由于两个流是独立的,因此客户端和服务器可以按任意顺序读写消息。
微服务
gRPC 强大之处的一个很好的例子是在微服务中。
在这个例子中,我们有用 Python、Java 和 GoLang 编写的微服务,需要在它们之间发送数据。
使用 HTTP/1.1 和 JSON 将需要您为每种语言实现 HTTP 连接和序列化。 您还需要确保为每种语言正确实施架构,如果 API 发生变化,则需要手动更新所有服务。
另一方面,gRPC 为我们处理了 HTTP/2.0 协议的实现。 编写单个模式,可以为所有使用的语言生成相应的代码。 这种模式可以看作是所有语言都需要遵守的契约,这使这些服务之间的通信更加容易和可靠。
gRPC 性能
gRPC 速度很快,通常比 REST 等价物的性能高得多:
- 协议缓冲区被序列化并作为二进制文件通过网络发送,这比普通的 JSON 消息小得多。
- gRPC 使用 HTTP/2.0 进一步改进
gRPC 有效压缩发送的数据具有显著优势,因为传输的数据负载越小,需要的 TCP 往返次数就越少。 最大传输单元 (MTU) 是表示联网设备将接受的最大数据包的大小,即 1,500 字节。
压缩是自动处理的,你只需使用 gRPC 就可以从中受益。 作为替代方案,可以在通过常规 HTTP 发送之前使用 GZIP 之类的东西来压缩 JSON 消息。 然而,这可能会带来不便,并增加了一层复杂性。 不同的语言和环境也可能对 GZIP 和其他等效压缩工具有不同级别的支持。 对于您使用的每种语言,您都需要自己重新实现正确的压缩和通信逻辑。 这与我们讨论的有关 HTTP 库的问题类似。
什么时候应该使用 gRPC?
如果您使用多种不同的编程语言,这些语言需要彼此紧密集成,并且需要快速频繁地发送大量数据的通信,那么 gRPC 将是完美的选择。
- 优点:
- 使用 gRPC 流式传输,可以轻松确定上传/下载进度,无需发出任何不必要的更新请求。
- 可以取消请求。
- HTTP/2 的所有优点。
- 如果 gRPC 支持你的语言,你就不必引入外部库。
- 缺点:
- gRPC 不支持所有语言。
- 该架构可能会让人感到限制和麻烦。
- 与 WebSockets 相比,它的设置可能很复杂。
- 出现时间较晚,错误可能难以调试。
- 与 gRPC 的通信本身并不适用于 Web 浏览器, 您需要使用 gRPC-Web 库。
WebRTC
WebRTC 特点
WebRTC 是一个免费的开源项目,可为基于开放标准运行的应用程序提供实时通信 (RTC) 功能。 它支持在对等点之间发送视频、语音和通用数据。
该技术作为一组适用于所有主流浏览器的 JavaScript API 和适用于 Android 和 iOS 应用程序等本机客户端的库提供。
WebRTC根本上不同于 WebSockets 和 gRPC,一旦建立连接,数据就可以(在某些情况下)直接在浏览器和设备之间实时传输,而无需接触服务器。
这减少了延迟并使 WebRTC 非常适合音频、视频或屏幕共享,低延迟并且可发送大量数据。
WebRTC 动机
WebRTC 旨在标准化媒体(例如音频和视频)如何通过线路进行通信,并通过简单易用的 API 方便地实现这一目标。
其他解决方案,例如 WebSockets,确实可以在两个对等点之间传输任何数据; 但是,此数据需要通过代理或服务器传输。 依赖另一台服务器会增加延迟,因为通过它发送的所有内容都需要接收、处理和解密。对于视频流甚至实时聊天,这种延迟是不可取的。
现在的浏览器也比几年前更强大。 浏览器可以访问网络摄像头和麦克风,需要内置 API 和一种简单的方法来传输这些丰富的信息。 WebRTC 旨在简化整个过程,并公开浏览器本机可用的,易于使用的 API。
WebRTC 问题
动机已经明确,WebRTC 似乎是一种神奇的解决方案,可以让两个对等方之间的通信更快。 但不幸的是,存在一些问题。
- 第一个问题是建立点对点连接并不简单,互联网很复杂。加利福尼亚的 Alice 和南非的 Ben 之间有很多路由器、代理和防火墙。 在某些情况下,可能无法在两个对等点之间建立直线。 两个对等点之间的连接可能需要绕过阻止打开连接的防火墙,您可能没有公共 IP 地址,或者路由器可能不允许对等点之间的直接连接。
- 第二个问题是需要有一种方法让两个对等点相互发现并确定可以进行通信的最佳路由。 这需要在两个客户端知道如何最好地相互通信之前在两个客户端之间共享某些信息,而共享此信息的一种常见方法是使用 WebSockets。
这有点好笑, 一个 HTTP 连接升级为 WebSocket 连接只是为了共享建立 WebRTC 连接的信息。
如果您真的想了解 WebRTC 的功能及其复杂性,您需要熟悉一些可能不熟悉的术语:NAT、STUN、TURN、ICE、SDP 和信令。
WebRTC 是如何工作的?
在上面的概述中,我们描述了 WebRTC 的动机,本节将深入探讨您需要了解的一些底层概念,以充分掌握 WebRTC。
网络地址转换 (NAT)
了解 NAT 是什么以及它如何工作对于理解 WebRTC 至关重要。NAT 用于为您的设备(笔记本电脑或手机)提供公共 IP 地址; 这很重要,因为我们要在可能都在路由器后面的两个对等点之间建立连接。 路由器将有一个公共 IP 地址,连接到路由器的每个设备都将有一个私有 IP 地址。
这些设备不直接暴露在互联网上。 相反,所有流量都通过与外界通信的路由器。 当您从远程服务器请求资源时,路由器负责将请求从本地计算机“路由”到该服务器,并将服务器的响应路由回本地计算机。
这些请求从设备的私有 IP 地址转换为具有唯一端口的路由器的公共 IP,然后存储在 NAT 表中。 这样,本地网络上的每个设备都没有必要拥有唯一的公共 IP。
上图是 NAT 表的简单示例。 假设私有 IP 为 192.168.1.50 的本地设备请求公共地址 82.88.31.26:80 获取一些数据。
这是通过本地设备首先向路由器发送请求,路由器将请求路由到远程设备来实现的。 路由器然后告诉远程设备将响应发送到其外部 IP 地址,具有唯一端口,在本例中为 86.88.71.25:8830。
这个唯一的端口很重要,因为它将允许路由器确定发出请求的本地设备。 所有这些信息都存储在 NAT 表中, 一旦路由器得到响应,它就可以执行查找并决定将响应转发到哪个本地设备。
当我们有一个正常的请求/响应对,即一个设备和一个服务器时,这很容易理解。 但是,如果另一个具有完全不同 IP 地址的外部设备决定将数据包发送到先前使用的同一端口上的路由器外部 IP 地址,会发生什么情况? 路由器是否应该将其转发到映射到该端口号的本地设备?
该决定取决于路由器使用哪种 NAT 转换,并最终确定是否可以建立对等连接。 根据您使用的路由器,它会执行不同的 NAT 转换。 有四种不同的 NAT 转换方法:
- 一对一 NAT
- 将一个外部 IP 地址和端口(通常是公共的)映射到一个内部 IP 地址和端口(通常是私有的)。 在上面的示例中,如果路由器在端口 8830 和外部 IP 86.88.71.25 上收到响应,它会将其转发到本地设备 192.168.1.50,因为这是发出请求的本地设备(从 NAT 表中检索的信息 ). 路由器不关心目标 IP 或响应的来源。 如果它在特定的外部端口上,它将转到该本地设备。
- 地址限制 NAT
- 仅当本地设备先前已将数据包发送到远程 IP 地址时,远程设备才能将数据包发送到本地设备。 总之,我们只有在之前与该主机通信过的情况下才允许它。 在上面的例子中,只允许来自 86.88.71.25 的数据包
- 端口限制 NAT
- 与地址限制 NAT 相同,但限制还包括端口号。 如果内部设备先前已将数据包发送到 IP 地址 X 和端口 P,则远程设备只能将数据包发送到内部设备。在上面的示例中,仅允许来自 86.88.71.25 和端口 80。
- 对称 NAT
- 限制最严。 为此,外部 IP、外部端口、目标 IP 和目标端口都必须与 NAT 表中的内容相匹配。 这意味着数据包只能发送到本地设备的特定端口,前提是该设备是请求目标 IP 和端口的设备。
WebRTC 不能在对称 NAT 上工作,要理解为什么我们需要理解什么是 STUN 服务器。
NAT 的会话遍历实用程序 (STUN)
STUN 是一种协议,可通过 NAT 告诉您您的公共 IP 地址/端口,并确定您的路由器中会阻止与对等方直接连接的任何限制。 STUN 服务器是一种机制,供客户端发现 NAT 的存在以及 NAT 的类型,并确定 NAT 的外部 IP 地址和端口映射。
一个 STUN 请求的目的是确定你的公开存在,这样这个公开存在就可以与其他人交流,就可以与你联系——这种交流被称为信号,我们将在后面详细讨论。
它适用于一对一、地址受限和端口受限的 NAT。 但不适用于对称 NAT。 因为当您向 STUN 服务器请求您的公共信息时,该通信对是专门为发出请求的客户端创建的。 使用对称 NAT 不可能涉及另一个对等点——通过本地设备端口的通信仅限于 STUN 服务器。
STUN 服务器重量轻,维护成本低。 有公共的 STUN 服务器可以免费查询。
下图说明了 STUN 何时工作以及何时可以建立对等连接。
另一方面,如果无法建立点对点连接,例如,当对等点位于对称 NAT 之后时——那么第三步中的最终连接将不会被允许。 由于初始连接是与 STUN 服务器建立的,没有其他对等方可以使用该连接信息。在无法建立直接连接的情况下,我们需要使用 TURN 服务器。
使用中继绕过 NAT 的遍历(TURN)
TURN 是一种协议,用在无法在两个对等点之间建立直接连接时中继网络流量。 例如,如果一个对等点位于对称 NAT 之后,则需要一台专用服务器来中继对等点之间的流量。 在那种情况下,您将创建一个与 TURN 服务器的连接,并告诉所有对等方将数据包发送到该服务器,然后这些数据包将转发给您。
这会带来开销,并且 TURN 服务器的维护和运行成本可能很高。下图说明了如何使用 TURN 服务器在两个或多个对等点之间中继消息。
交互式连接建立(ICE)
ICE 使用 STUN 和 TURN 协议的组合,为主机提供一种机制来发现彼此的公共 IP 地址并建立直接连接。 如果无法直接连接,ICE 将使用 TURN 在两台主机之间建立中继连接。
所有这些可能建立连接的可能方式都称为 ICE 候选者。 所有收集到的地址都通过 SDP 发送到远程对等方,我们将在接下来进行探讨。 WebRTC 在每个客户端上使用此信息来确定连接到另一个对等点的最佳方式。 可能是两个对等点都在同一个 NAT 上并且可以建立本地连接,或者可能是两个对等点都在对称 NAT 后面并且需要使用 TURN 服务器的中继。
会话描述协议 (SDP)
SDP 本质上是一种描述媒体会话的数据格式,用于会话公告、会话邀请和其他形式的会话发起。 它是描述连接的多媒体内容的标准,例如分辨率、格式、编解码器和加密。
重要的是,它还用于描述 ICE 候选人和其他网络选项。 当对等点 A 想要连接到对等点 B 时,他们需要共享 SDP 信息才能连接。 这个 SDP 如何共享完全取决于——这被称为信令,我们将在接下来探讨它。
Signaling-建立连接
信令是在两个设备之间发送控制信息以确定通信协议、信道、媒体编解码器和格式、数据传输方法以及任何所需路由信息的过程。 关于 WebRTC 的信令过程,最重要的是要知道:它没有在规范中定义。
对等连接处理连接不同计算机上的两个应用程序。 连接是通过称为信令的发现和协商过程建立的。
一个重要的警告是 WebRTC 没有内置信号作为规范的一部分,因为两个设备不可能直接相互联系,我们之前详细探讨过这一点。 对于使用 WebRTC 连接的两个对等点,它们需要彼此的 SDP 数据。
因此,作为开发人员,您有责任为两个设备建立一种共享此信息的方式。 一个流行的选项是 WebSockets,或者可以通过电子邮件来回发送信令信息或步行传递并手动输入以建立连接。
一旦共享了此信息,您就拥有了两个对等点建立 WebRTC 连接所需的一切,它可能是直接连接,也可能是通过 TURN 服务器。
什么时候应该使用 WebRTC?
你甚至可能会问:我为什么要使用 WebRTC? 理解起来似乎很复杂,设置起来更复杂。但有很多好处:
- API 易于使用,可直接在您的浏览器中使用。
- 它具有良好的性能,可以传输高带宽内容,例如视频或音频。
- 可以轻松实现更多高级功能,例如屏幕共享和文件共享。
- 支持减少延迟的点对点连接。
- 免费和开源。
但是 WebRTC 也有缺点:
- 没有内置信号。
- 您需要维护 STUN 和 TURN 服务器。
- 对于群组连接(例如群组视频通话),可能需要 SFU。
- 设置和理解起来很复杂。
HTTP、WebSockets、gRPC、WebRTC 如何选择?
具体选择哪个协议要视情况而定,没有一个统一的答案,但是可以从宏观上对比下几种协议。
HTTP:使用 HTTP/2 可以在客户端和服务器之间进行双向通信。 对于某些应用程序,您可能不需要全双工通信,像 SSE 这样的东西就足够了。 我们在本文中也发现 WebSockets 和 gRPC 依赖于 HTTP,而 WebRTC 也需要一些其他的信令通道。 在深入研究这些其他协议之前,值得首先探索 HTTP 是否能满足您的实际需求。
WebSockets: 最适合需要双向通信的实时应用程序,例如聊天应用程序, WebSockets 也相对容易设置和使用。 但是,WebSockets 的效率不如 gRPC 或 WebRTC,它们不太适合需要发送大量数据的应用程序。
gRPC: 是一种比 WebSockets 更高效的协议,更适合需要发送大量数据的应用程序。 但是,gRPC 的设置和使用比 WebSockets 更复杂。 如果你需要进行很多小的 API 调用,gRPC 是一个不错的选择。 或者,当你用各种需要通信的编程语言实现微服务时,那么 gRPC 的序列化结构化数据和代码生成会为你节省大量时间。 值得注意的是,您无法在浏览器端轻松使用 gRPC,当然您可以手动引入 grpc-web。
WebRTC: 是浏览器和设备之间低延迟实时通信的最有效协议,非常适合需要发送大量数据的应用程序。 WebRTC 还提供了简单易用的 API,可直接在浏览器中使用,从而轻松共享您的相机、音频、屏幕或其他文件。 但是,WebRTC 的设置和使用可能很复杂,因为它需要您执行信号发送,维护 TURN 和 STUN 服务器。
结论
未来将会看到更多的协议、变化和进一步的改进。目前 HTTP/3 已经发布,还有一个名为 WebTransport 的新通信协议,它有可能替代 WebSockets。
翻译自:https://getstream.io/blog/communication-protocols/