From a9489ee887451ac98b87d3abca78dc80530093bc Mon Sep 17 00:00:00 2001 From: Jerry Lee Date: Tue, 19 Feb 2019 19:17:45 +0800 Subject: [PATCH] import rsocket translation of @beiwei30 --- rsocket/README.md | 291 ++++++++++ rsocket/protocol.md | 1315 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1606 insertions(+) create mode 100644 rsocket/README.md create mode 100644 rsocket/protocol.md diff --git a/rsocket/README.md b/rsocket/README.md new file mode 100644 index 0000000..b130af8 --- /dev/null +++ b/rsocket/README.md @@ -0,0 +1,291 @@ +原文链接: [Motivations · RSocket](http://rsocket.io/docs/Motivations) - https://github.com/rsocket/rsocket + +## 🍎 译序 + +译文由阿里中间件的 [罗毅(北纬)](https://yq.aliyun.com/articles/593279) 提供,感谢翻译! + +# 动机 + +大型分布式系统往往通过模块化的方式来构建,不同团队可能采用不同的技术和不同的编程语言来实现其中的模块。这些模块需要可靠的通讯,并支持快速独立的演进。在分布式系统中,一个至关重要、需要考虑的因素是模块之间需要具备高效可扩展的通讯机制。这个因素将会显著的影响到用户能够感受到的网络延迟、以及构建并运行系统所需要消耗的资源。 + +在[Reactive 宣言](https://www.reactivemanifesto.org)中提及并由 [Reactive Streams](http://www.reactive-streams.org) 和 [Reactive Extensions](http://reactivex.io) 实现的架构模式倡导异步消息机制以及拥抱 request/response 之上的通讯模型。本文中的 "RSocket" 协议是一个遵循 "reactive" 准则的通讯协议。 + +以下是为什么重新定义一个新的协议的动机: + +---------------------------------------- + + + + + +- [消息驱动](#%E6%B6%88%E6%81%AF%E9%A9%B1%E5%8A%A8) +- [交互模型](#%E4%BA%A4%E4%BA%92%E6%A8%A1%E5%9E%8B) + - [发射后不管](#%E5%8F%91%E5%B0%84%E5%90%8E%E4%B8%8D%E7%AE%A1) + - [Request/Response (单一响应)](#requestresponse-%E5%8D%95%E4%B8%80%E5%93%8D%E5%BA%94) + - [Request/Stream (多个 response,有限个)](#requeststream-%E5%A4%9A%E4%B8%AA-response%E6%9C%89%E9%99%90%E4%B8%AA) + - [Channel](#channel) +- [行为](#%E8%A1%8C%E4%B8%BA) + - [单个 response 对比多个 response](#%E5%8D%95%E4%B8%AA-response-%E5%AF%B9%E6%AF%94%E5%A4%9A%E4%B8%AA-response) + - [双向](#%E5%8F%8C%E5%90%91) + - [取消](#%E5%8F%96%E6%B6%88) +- [可恢复](#%E5%8F%AF%E6%81%A2%E5%A4%8D) +- [应用层流量控制](#%E5%BA%94%E7%94%A8%E5%B1%82%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6) + - ["Reactive Streams" `request(n)` 异步拉取](#reactive-streams-requestn-%E5%BC%82%E6%AD%A5%E6%8B%89%E5%8F%96) + - [租约](#%E7%A7%9F%E7%BA%A6) +- [多语言支持](#%E5%A4%9A%E8%AF%AD%E8%A8%80%E6%94%AF%E6%8C%81) +- [传输层上的灵活性](#%E4%BC%A0%E8%BE%93%E5%B1%82%E4%B8%8A%E7%9A%84%E7%81%B5%E6%B4%BB%E6%80%A7) +- [效率 & 性能](#%E6%95%88%E7%8E%87--%E6%80%A7%E8%83%BD) +- [比较](#%E6%AF%94%E8%BE%83) + - [TCP & QUIC](#tcp--quic) + - [WebSockets](#websockets) + - [HTTP/1.1 & HTTP/2](#http11--http2) + + + +---------------------------------------- + +## 消息驱动 + +网络通讯是异步的。RSocket 协议遵从该点并将所有通讯模型都建模为单个网络连接上多路传输的消息流,并且,在这个连接上永远不会因为等待一个 response 而同步阻塞。 + +[Reactive 宣言](http://www.reactivemanifesto.org)申明: + +> Reactive 系统依赖异步消息传递划分组件之间的边界,从而保证了松耦合、隔离、位置透明、并提供了通过消息传递错误的手段。明确的消息传递机制的运用,使得通过调整和观测系统中的消息队列并按需调整反压策略来进行负载管理、弹性、流控成为可能。非阻塞的通讯容许接受方仅在激活的时候消耗资源,从而减少了系统开销。 + + +此外, [HTTP/2 FAQ](https://http2.github.io/faq/#why-is-http2-multiplexed) 对于为何使用长连接上的多路复用、面向消息的协议有很好的解释: + +> HTTP/1.x 中存在一个所谓 "head-of-line blocking" 的问题,导致一条连接上在同一时间内只能有一个 request。 + + +> HTTP/1.1 试图通过 pipeling 来解决这个问题,但是这个方案并不能完全的解决 (一个大的或者慢的 request 仍然会阻塞后续的 request)。另外,pipelining 这个方案部署起来十分困难,因为很多代理和服务器并不能正确的处理。 + + +> 这种情况迫使客户端使用一系列启发式的手段 (或者称之为猜) 来决定当有多条连接的情况下(一个页面加载时往往有十条甚至更多的连接建立)请求应该由哪条连接来发送。这样做可能在性能上带来极大的影响,往往导致 “瀑布式” 的请求阻塞。 + + +> 多路复用通过容许同一时间接受多个 request 和 response 消息通过很好的解决了这个问题。它甚至容许消息之间的交错传输。 + + +> 这样,就达成了容许客户端在加载页面时为每一个来源只需要建立一条连接即可。 + + +在这篇文章里还讨论了长连接: + +> 在 HTTP/1 中,浏览器往往为每一个来源建立四到八条连接。由于许多网站引用了多个来源,这就意味着加载一个页面可能需要打开超过三十条连接。 + + +> 一个应用在同一时刻打开如此多的连接打破了 TCP 协议中制定的很多假设。由于每一条连接上都存在一系列 response 数据的传回,会导致网络上缓冲区的溢出,从而导致网络拥塞和数据重传的发生。 + + +> 此外,同时使用如此多的连接会霸占过多的网络资源,对于其他有礼貌的应用不公平(比如,VoIP)。 + + +## 交互模型 + +一个不当的协议会增加一个系统的开发成本。一个不匹配的抽象会限制系统的设计,进而导致开发人员为了找到变通的手段来解决错误和性能上的问题而花费额外的时间。这个问题在多语言环境中还会被放大,因为不同的语言中变通方法是不一样的,并且团队之间也需要更多的协作。当下事实上的标准是 HTTP,这个协议中所有的内容都是关于 request/response。在一些场景下 request/response 并不是理想的通讯模型。 + +一个可能的例子是通知的推送。使用 request/response 模型迫使应用通过客户端不断发送请求到服务器端来检查是否有新的数据。服务器处理大量请求并告知客户端没有数据更新的例子在现实场景里屡见不鲜,这对于客户端、服务器、网络、金钱、基础设施、运维复杂度、以及根子上的系统可用性都是一种浪费。这对于接收通知的用户体验上也增加了延迟,原因是 polling 这种方式在减少延迟的尝试上是一种倒退。 + +由于这样和那样的原因,RSocket 协议被设计成不仅仅只有一种交互模型。下面描述的不同的交互模型使得系统设计有了新的可能性: + +### 发射后不管 + +发射后不管 (Fire-and-forget) 是 request/response 模型中不需要返回 response 的一个特例。这个模型可以显著的减少性能上的开销,原因是它不但不用在网络上传输 response 消息,而且客户端和服务器不需要等待对应的 response 或者取消 request 从而避免了额外的处理逻辑。 + +这个交互模型在消息容许丢失的场景下有用,比如非关键的事件记录 (event logging) 场景。 + +使用这种模式的代码: + +```java +Future completionSignalOfSend = socketClient.fireAndForget(message); +``` + +### Request/Response (单一响应) + +标准的 request/response 语义仍然被支持,并且还会是 RSocket 连接上的主要的使用方式。这种 request/response 的交互可以被认为是 "只有一个 response 的消息流" 的特例,并且是单一网络连接上多路传输的异步消息。 + +客户端 "等待" 一个 response 消息的返回,从而看起来像是一个典型的 request/response,但是底层从来就不会同步阻塞。 + +使用这种模式的代码: + +```java +Future response = socketClient.requestResponse(requestPayload); +``` + +### Request/Stream (多个 response,有限个) + +request/response 进一步的扩展是 request/stream,这个模式容许多个消息流式返回。可以把这种模式想象成 response 对象是一个 "collection" 或者一个 "list",只不过不是通过单一的 reponse 返回,而是其中的每一个元素按序返回。 + +使用场景包括: + +* 获取视频列表 +* 获取某个类目下的所有商品 +* 按行获取文件内容 + + +使用这种模式的代码: + +```java +Publisher response = socketClient.requestStream(requestPayload); +``` + +### Channel + +一个 channel 是双向的,并且在每个方向上都有一系列的消息传输。 + +一个能够借助这种交互模式的例子是: + +* 客户端请求一组数据来构建世界的当前视图 +* 当数据发生改变时,服务器将 delta/diff 发送回客户端 +* 期间客户端也可以更新订阅,增加或者删除感兴趣的主题、条件、等等 + + +如果没有双向的 channel,客户端就得取消初始的请求,重新发送新的请求并不得不重新接受所有的数据,而不是简单的更新订阅的主题,仅仅接受其中的改变。 + +使用这种模式的代码: + +```java +Publisher output = socketClient.requestChannel(Publisher input); +``` + +## 行为 + +除了交互模式以外,其他的一些行为也会使得系统和应用在效率方面受益。 + +### 单个 response 对比多个 response + +单个 response 和多个 response 中一个关键的区别是数据在 RSocket 上如何传输:一个 single-response 消息可能由多个 frame 传输,传输的连接是多路复用的,其他的消息可能也同时在此连接上传输。single-response 模式下,应用只能在接收完所有数据才能开始后续处理。另一方面,multi-response 零碎地发送数据。这样,用户在设计服务的时候可以借助 multi-response 的特性,在接收到第一份数据的时候就可以开始处理。 + +### 双向 + +RSocket 支持双向的请求,也就是说客户端和服务器可以互为对方的请求方和接收方。这样就容许一个客户端(比如,用户设备)成为来自服务器端请求的接收方。 + +例如:服务器可以询问客户端的 debug 信息、状态等。这样做可以减少系统扩容的压力,因为避免了成千上万无谓的来自客户端的数据上报,而替换为了服务端按需查询。这种行为也开启了目前还没有预见到的客户端和服务端新的交互模型。 + +### 取消 + +所有的消息流(包括 request 和 response)支持取消的行为使得服务器(消息回应方)可以有效的清理资源。比如,当客户端取消,或者离线,服务器有机会更早的终止相关的处理工作。这个行为不但对于 stream 和 subscription 这类交互模型是必要的,而且对于 request/response 这种模式更为有用,因为这样做对于运用类似 "backup requests" 这样的手段来处理尾延迟这类问题更加有效。 (更多相关信息请参阅[这里](http://highscalability.com/blog/2012/3/12/google-taming-the-long-latency-tail-when-more-machines-equal.html),[这里](http://highscalability.com/blog/2012/6/18/google-on-latency-tolerant-systems-making-a-predictable-whol.html),[这里](http://www.bailis.org/blog/doing-redundant-work-to-speed-up-distributed-queries/),以及[这里](http://static.googleusercontent.com/external_content/untrusted_dlcp/research.google.com/en/us/people/jeff/Stanford-DL-Nov-2010.pdf)) + +## 可恢复 + +对于持久消息流的场景,尤其是那些服务来自移动客户端订阅的场景,如果在网络连接断开时所有的订阅必须重新建立,将会极大的影响性能开销。在以下的情况发生的时候这个问题会更加严重:网络连接立刻又重新建立起来,或者在 Wifi 和移动网络之间相互切换时。 + +RSocket 支持会话级别的恢复,容许通过一次简单的握手在新的网络连接上恢复客户端服务端之间的会话。 + +## 应用层流量控制 + +RSocket 支持两种形式的应用层流控来保护客户端和服务端资源不被滥用。 + +本协议被设计成既可以使用在数据中心、服务器到服务器之间的通讯,也适用于通过互联网在服务器和设备之间的通讯,比如:服务器和移动设备或者浏览器之间的通讯。 + +### "Reactive Streams" `request(n)` 异步拉取 + +这种形式的流控既适用于服务器-服务器场景,也适用于服务器-设备的场景。该流控形式借鉴了 Reactive Streams 中的 [Subscription.request(n)](https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.0/README.md#3-subscription-code) 行为。在 [RxJava](https://github.com/ReactiveX/RxJava/),[Reactor](https://github.com/reactor/reactor),以及 [Akka Streams](http://doc.akka.io/docs/akka/2.4/scala/stream/index.html) 中都有这种所谓 "异步拉-推" 流控的实现。 + +RSocket 容许跨越网络边界在请求方和回应方之间(通常是客户端和服务端)构建 `request(n)` 的信号。这种从回应方向请求方的流量控制是通过在应用层的 Reactive Streams 语义来达成的,并且由于有界缓冲的接入使得流控与应用消费的速率相适配,而不是完全依靠网络层和传输层的缓冲。 + +相同的数据类型和做法也被 Java 9 中的 `java.util.concurrent.Flow` [suite of types](http://download.java.net/java/jdk9/docs/api/java/util/concurrent/Flow.Subscription.html) 采用。 + +### 租约 + +第二种类型的流控主要关注在一个数据中心内服务器-服务器之间的场景。该类型流控作用的前提下,一个回应方(通常是一个服务器)可以发送有关自己处理能力的租约给请求方,以期控制请求速率。在请求端,应用层的负载均衡将仅发送消息给通知过处理能力的回应方。基于这个来自服务器的信号,在数据中心的一个机器集群上实现一个更加智能的路由和负载均衡算法成为了可能。 + +## 多语言支持 + +上面提到的本协议的动机中很多都可以借助现有的协议、库、和技术得以实现。但是,这样往往导致与特定的实现强绑定,该实现必须能够做到跨语言、跨平台、跨技术栈。相反的,将交互模式和流量控制的行为定义在协议里,为不同语言中的实现提供了契约。与普遍存在的 HTTP/1.1 request/response 相比,通过更丰富的行为增强了多语言交互,同时,也使得 Reactive Stream 应用级别的跨语言流控成为可能 (而不仅仅是 Reactive Streams 最初定义的那样只适用于 Java)。 + +## 传输层上的灵活性 + +就如同 HTTP request/response 不是应用唯一的通讯手段,TCP 也不是传输层唯一的选择,甚至在某些场景下不是最佳选择。所以,RSocket 容许根据环境、设备的能力以及性能上的诉求来选择底层的传输层。RSocket (应用协议)可以基于 WebSockets、TCP、以及 [Aeron](https://github.com/real-logic/Aeron),并且,也可以基于其他类似 TCP 的传输层协议,比如: [Quic](https://www.chromium.org/quic)。 + +也许更重要的原因是,基于 TCP、WebSockets 以及 Aeron 很容易实现。比如,采用 WebSockets 往往比较有吸引力,不过它暴露的是 framing 的语义,所以需要额外定义应用层的协议,这往往需要花费极大的努力才能实现。TCP 甚至都不提供 framing 的语义。所以,最终大部分应用只能采用 HTTP/1.1 中的 request/response,并因此丧失了使用同步 request/response 以外的交互模型的好处。 + +因此 RSocket 选择了在这些网络传输层之上定义应用层语义,从而使得它们可以根据实际情况来自由选择。这篇文档的后面会提供一个与其他协议的简要比较,目的是在决定引入一个新的应用层协议之前试试 WebSockets 和 Aeron 是否可以利用。 + +## 效率 & 性能 + +一个使用网络资源低效的协议(重复的握手、连接建立和取消、臃肿的消息格式等)往往极大的增加一个系统中可察觉的延迟。另外,如果没有流量控制的语义,一个实现很差的模块在其所依赖的服务变慢的时候过载整个系统,很有可能会引起重试风暴从而增加系统的压力。[Hystrix](https://github.com/Netflix/Hystrix/wiki#problem) 是一个试图解决同步 request/response 问题的例子,但是它也[带来了](https://github.com/Netflix/Hystrix/wiki/FAQ#what-is-the-processing-overhead-of-using-hystrix)额外的系统负担和复杂度。 + +还有,一个选错的通讯协议还会浪费服务器资源(CPU、内存、网络带宽)。对于较小规模的部署也许还可以接受,但是对于具有成百上千节点的大型系统,很小程度的效率低的问题会被急剧放大。虽然现在服务器资源很便宜但是也有上限,因为 footprint 越大,留给纵向扩展的空间就越小。即便有很好的工具,管理大型集群也是十分昂贵和笨拙的。而且常常被忽略的是,集群越大,管理的复杂度也就越大,并进一步成为可用性上的隐患。 + +RSocket 寻求的是: + +* 通过支持非阻塞、多路复用、异步的应用层通讯来降低可察觉的延迟,提升系统的效率。同时该通讯还支持通过任何语言来控制多个传输通道上的流量。 + + +* 通过以下措施降低硬件规模 (也就是成本和维护的复杂度): + * 通过二进制编码增加 CPU 和内存的使用效率 + * 容许持久连接来避免冗余无效的工作 + + +* 通过以下的手段来降低可察觉的延迟: + * 避免握手从而避免了与之对应的网络往返 + * 通过二进制编码的使用降低了计算时间 + * 分配更少内存从而降低 GC 开销 + + +## 比较 + +以下是在决定引入 RSocket 之前对一些现有协议的简要调研。这个调研不是完整全面的调研,同时也不是针对这些协议的批评,这些协议都很适合它们最初被引入的场景。本章节的主要目的是想说明现有的这些协议都不能很好的满足 RScocket 之所以被引入的动机。 + +背景: + +* RSocket 是 OSI 5/6 层或者 TCP/IP 应用层的协议。 +* RSocket 被设计成全双工、二进制传输,并具备类似 TCP 行为的协议(更详细的描述请参阅[这里](https://github.com/RSocket/reactivesocket/blob/master/Protocol.md#transport-protocol))。 + + +### TCP & QUIC + +没有 framing 和应用语义。必须提供一个应用协议。 + +### WebSockets + +没有应用语义,只有 framing。必须提供一个应用协议。 + +### HTTP/1.1 & HTTP/2 + +HTTP 为应用协议的构建提供了一个刚刚够用的能力,但是一个应用层的协议仍然需要在其上定义。对于定义应用语义来说它是不够的(([Google 的 GRPC](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md) 就是一个在 HTTP/2 之上构建这些语义的例子)。 + +这些有限的应用语义通常要求应用层协议定义以下几点: +* 使用 GET、POST 或者 PUT 发送请求 +* 使用 Normal、Chunked 或者 SSE 发送回应 +* payload 的 MimeType +* 带有标准错误码的错误消息 +* 客户端如何处理状态码的行为 +* 在持久连接上使用 SSE 来实现服务器到客户端的推送 + + +HTTP 中没有从回应方(往往是服务器)到请求方(往往是客户端)的流控机制。HTTP/2 有字节层面的流控,但是在应用级别并没有。通知请求方(通常来自服务器端)可用性(比如请求失败)的机制低效并且痛苦。协议缺乏对类似发射后不管等交互模式的支持,同时 streaming 模式支持也不完备 (chunked 编码和 SSE 仅支持 ASCII)。 + +虽然 REST 被广泛使用,但是它在定义应用语义方面是不充分的也是不合适的。 + +那么 HTTP/2 呢?难道它不是用来解决 HTTP/1 的问题,以及 RSocket 协议中提出的这些动机吗? + +很不幸的是,并不能。HTTP/2 在浏览器以及 request/resonse 文档传输等方面可以很好的工作,但是它没有为应用暴露出如同本文描述的那种行为和交互模式。 + +下文引用了 HTTP/2 [spec](https://http2.github.io/http2-spec/) 和 [FAQ](https://http2.github.io/faq/) 以方便理解 HTTP/2 被引入的上下文: + +> “HTTP 现有的语义并未发生改变。” + + +> “… 从应用的角度,绝大部分协议中的特性并未改变 …” + + +> "这个工作专注在 wire protocol 上的修正 - 比如,如何在网络上传输 HTTP header、method 等等,而不是改变 HTTP 的语义。" + + +另外,"push promises" 主要专注在为标准 web 浏览行为填充浏览器缓存: + +> “推送的回应总是与一个特定的来自客户端的请求相关联。” + + +这就代表着我们仍然需要 SSE 或者 WebSockets (并且 SSE 是文本协议,要求编码是 Base64 而不是 UTF-8) 来做推送。 + +HTTP/2 比 HTTP/1.1 好,主要是从网站上如何获取文档的方面来说的。从应用方面来说,我们可以做的比 HTTP/2 更好。 + +  + +----------------- + +      [协议 »](protocol.md) diff --git a/rsocket/protocol.md b/rsocket/protocol.md new file mode 100644 index 0000000..48efc27 --- /dev/null +++ b/rsocket/protocol.md @@ -0,0 +1,1315 @@ +# 协议 + +## 状态 + +本协议目前处于草案状态。 +当前协议版本是 __0.2__ (主版本:0,辅版本:2)。本版本是 1.0 发布版本的候选。不久的将来会为了发布 1.0 版本在 Java 和 C++ 的实现上做最终的测试。 + +## 介绍 + +为异步、双向的 [Reactive Streams](http://www.reactive-streams.org/) 语义指定一个应用层的协议。更多细节请参阅 [rsocket.io](http://rsocket.io/)。 + +RSocket 假设了一种操作范式。这些假设包括: + +* 一对一的通讯 +* 没有被代理的通讯。或者被代理的时候,RSocket 语义和假设被代理遵从。 +* 协议不会在 [传输协议](#transport-protocol) 会话之间保存状态 + +本文中使用的关键词遵从 [RFC 2119](https://tools.ietf.org/html/rfc2119) 中的含义。 + +所有字段的字节序是 big endian。 + +## 目录 + + + + + +- [协议](#%E5%8D%8F%E8%AE%AE) + - [状态](#%E7%8A%B6%E6%80%81) + - [介绍](#%E4%BB%8B%E7%BB%8D) + - [目录](#%E7%9B%AE%E5%BD%95) + - [术语](#%E6%9C%AF%E8%AF%AD) + - [版本号说明](#%E7%89%88%E6%9C%AC%E5%8F%B7%E8%AF%B4%E6%98%8E) + - [跨版本兼容性](#%E8%B7%A8%E7%89%88%E6%9C%AC%E5%85%BC%E5%AE%B9%E6%80%A7) + - [数据和元信息](#%E6%95%B0%E6%8D%AE%E5%92%8C%E5%85%83%E4%BF%A1%E6%81%AF) + - [组帧 (Framing)](#%E7%BB%84%E5%B8%A7-framing) + - [Transport 协议](#transport-%E5%8D%8F%E8%AE%AE) + - [组帧 (Framing) 协议的用法](#%E7%BB%84%E5%B8%A7-framing-%E5%8D%8F%E8%AE%AE%E7%9A%84%E7%94%A8%E6%B3%95) + - [组帧格式](#%E7%BB%84%E5%B8%A7%E6%A0%BC%E5%BC%8F) + - [Frame 头的格式](#frame-%E5%A4%B4%E7%9A%84%E6%A0%BC%E5%BC%8F) + - [处理 Ignore 标记](#%E5%A4%84%E7%90%86-ignore-%E6%A0%87%E8%AE%B0) + - [Frame 校验](#frame-%E6%A0%A1%E9%AA%8C) + - [可选的元数据头](#%E5%8F%AF%E9%80%89%E7%9A%84%E5%85%83%E6%95%B0%E6%8D%AE%E5%A4%B4) + - [Stream 标识](#stream-%E6%A0%87%E8%AF%86) + - [生成](#%E7%94%9F%E6%88%90) + - [生命周期](#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F) + - [Frame 类型](#frame-%E7%B1%BB%E5%9E%8B) + - [SETUP Frame (0x01)](#setup-frame-0x01) + - [ERROR Frame (0x0B)](#error-frame-0x0b) + - [错误码](#%E9%94%99%E8%AF%AF%E7%A0%81) + - [LEASE Frame (0x02)](#lease-frame-0x02) + - [KEEPALIVE Frame (0x03)](#keepalive-frame-0x03) + - [REQUEST_RESPONSE Frame (0x04)](#request_response-frame-0x04) + - [REQUEST_FNF (Fire-n-Forget) Frame (0x05)](#request_fnf-fire-n-forget-frame-0x05) + - [REQUEST_STREAM Frame (0x06)](#request_stream-frame-0x06) + - [REQUEST_CHANNEL Frame (0x07)](#request_channel-frame-0x07) + - [REQUEST_N Frame (0x08)](#request_n-frame-0x08) + - [CANCEL Frame (0x09)](#cancel-frame-0x09) + - [PAYLOAD Frame (0x0A)](#payload-frame-0x0a) + - [METADATA_PUSH Frame (0x0C)](#metadata_push-frame-0x0c) + - [EXT (Extension) Frame (0x3F)](#ext-extension-frame-0x3f) + - [恢复操作](#%E6%81%A2%E5%A4%8D%E6%93%8D%E4%BD%9C) + - [假设](#%E5%81%87%E8%AE%BE) + - [隐式位置](#%E9%9A%90%E5%BC%8F%E4%BD%8D%E7%BD%AE) + - [客户端生命管理](#%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%94%9F%E5%91%BD%E7%AE%A1%E7%90%86) + - [恢复操作](#%E6%81%A2%E5%A4%8D%E6%93%8D%E4%BD%9C-1) + - [RESUME Frame (0x0D)](#resume-frame-0x0d) + - [RESUME_OK Frame (0x0E)](#resume_ok-frame-0x0e) + - [Keepalive 位置字段](#keepalive-%E4%BD%8D%E7%BD%AE%E5%AD%97%E6%AE%B5) + - [身份标识的处理](#%E8%BA%AB%E4%BB%BD%E6%A0%87%E8%AF%86%E7%9A%84%E5%A4%84%E7%90%86) + - [连接建立](#%E8%BF%9E%E6%8E%A5%E5%BB%BA%E7%AB%8B) + - [协商](#%E5%8D%8F%E5%95%86) + - [不带 LEASE 的序列](#%E4%B8%8D%E5%B8%A6-lease-%E7%9A%84%E5%BA%8F%E5%88%97) + - [带 LEASE 的序列](#%E5%B8%A6-lease-%E7%9A%84%E5%BA%8F%E5%88%97) + - [分段和重组](#%E5%88%86%E6%AE%B5%E5%92%8C%E9%87%8D%E7%BB%84) + - [PAYLOAD Frame](#payload-frame) + - [REQUEST Frames](#request-frames) + - [Stream 序列和存活时间](#stream-%E5%BA%8F%E5%88%97%E5%92%8C%E5%AD%98%E6%B4%BB%E6%97%B6%E9%97%B4) + - [Request Response](#request-response) + - [Request Fire-n-Forget](#request-fire-n-forget) + - [Request Stream](#request-stream) + - [Request Channel](#request-channel) + - [请求方和回应方的 COMPLETE 共存](#%E8%AF%B7%E6%B1%82%E6%96%B9%E5%92%8C%E5%9B%9E%E5%BA%94%E6%96%B9%E7%9A%84-complete-%E5%85%B1%E5%AD%98) + - [请求方的错误,回应方终止](#%E8%AF%B7%E6%B1%82%E6%96%B9%E7%9A%84%E9%94%99%E8%AF%AF%E5%9B%9E%E5%BA%94%E6%96%B9%E7%BB%88%E6%AD%A2) + - [请求方的错误,回应方已经完成](#%E8%AF%B7%E6%B1%82%E6%96%B9%E7%9A%84%E9%94%99%E8%AF%AF%E5%9B%9E%E5%BA%94%E6%96%B9%E5%B7%B2%E7%BB%8F%E5%AE%8C%E6%88%90) + - [回应方错误,请求方终止](#%E5%9B%9E%E5%BA%94%E6%96%B9%E9%94%99%E8%AF%AF%E8%AF%B7%E6%B1%82%E6%96%B9%E7%BB%88%E6%AD%A2) + - [回应方错误,请求方已经完成](#%E5%9B%9E%E5%BA%94%E6%96%B9%E9%94%99%E8%AF%AF%E8%AF%B7%E6%B1%82%E6%96%B9%E5%B7%B2%E7%BB%8F%E5%AE%8C%E6%88%90) + - [请求方取消,回应方终止](#%E8%AF%B7%E6%B1%82%E6%96%B9%E5%8F%96%E6%B6%88%E5%9B%9E%E5%BA%94%E6%96%B9%E7%BB%88%E6%AD%A2) + - [流量控制](#%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6) + - [Reactive Streams 语义](#reactive-streams-%E8%AF%AD%E4%B9%89) + - [租约语义](#%E7%A7%9F%E7%BA%A6%E8%AF%AD%E4%B9%89) + - [服务质量和优先级](#%E6%9C%8D%E5%8A%A1%E8%B4%A8%E9%87%8F%E5%92%8C%E4%BC%98%E5%85%88%E7%BA%A7) + - [意外的处理](#%E6%84%8F%E5%A4%96%E7%9A%84%E5%A4%84%E7%90%86) + + + +## 术语 + +* __Frame__: 一个单一的消息,其中包含了一个请求、一个回应、或者协议的处理。 +* __Fragment__: 一个应用消息的一部分,被分段以便可以被包含在一个 Frame 中。参见 [分段与重组](#fragmentation-and-reassembly). +* __Transport__: 用于搭载 RSocket 协议的协议。WebSockets、TCP、或者 Aeron 中的一个。Transport **必须** 提供在 [transport protocol](#transport-protocol) 章节中提到的能力。 +* __Stream__: 操作单位(request/response 等)。参见[动机](Motivations.md)。 +* __Request__: 一个 stream 请求。可能是四种类型中的一个。也可以是请求更多的请求或者说取消上一次请求的请求。 +* __Payload__: 一个 stream 消息(上游或者下游)。包含与上次请求创建的 stream 想关联的数据。在 Reactive Streams 和 Rx 中这代表 'onNext' 事件。 +* __Complete__: 终止一个 stream 上事件的发送并示意成功完成。在 Reactive Streams 和 Rx 中代表 'onComplete' 事件。 + * 在本文档中,一个带有 Complete 标志位的 frame (PAYLOAD 或者 REQUEST_CHANNEL) 有时也看做是 COMPLETE,只要该 frame 的引用在语义上是关于 Complete 位/事件即可。 +* __Client__: 发起连接的一方。 +* __Server__: 接受来自客户端连接的一方。 +* __Connection__: 客户端和服务端之间的 transport 会话实例。 +* __Requester__: 发送请求的一方。一条连接上最多有两个请求方。一头一个。 +* __Responder__: 接受请求的一方。一条连接上最多有两个回应方。一头一个。 + +## 版本号说明 + +RSocket 的版本由一个数字主版本和一个数字辅版本组成。 + +### 跨版本兼容性 + +RSocket 假设所有版本(包括主版本和辅版本)都保持向前兼容性。 +一个客户端可以通过 [Setup Frame](#frame-setup) 来传递它所支持的版本。 +由服务端自主决定是否接受来自客户端的比其所能支持的版本更低的版本。 + +## 数据和元信息 + +RSocket 为应用提供了将 payload 区分成两种类型的机制。数据和原信息。至于二者之间的差别由应用自主定义。 + +以下是数据与元信息的一些特征。 + +* 元信息可以使用与数据不同的编码。 +* 元信息上可以"附着"(比如,与之关联)以下的实体: + * 通过元信息推送和连接 (Stream ID 为 0) + * 单独的 Request 或者 Payload (上游或者下游) + +## 组帧 (Framing) + +### Transport 协议 + +RSocket 协议使用更底层的 transport 协议来搭载 RSocket 的 frame。一个 transport 协议 **必须** 提供以下能力: + +1. 单播[可靠传输](https://en.wikipedia.org/wiki/Reliability_(computer_networking))。 +2. [面向连接](https://en.wikipedia.org/wiki/Connection-oriented_communication)以及保持 frame 有序。在 Frame B 之前发送的 Frame A 必须按照原始顺序首先到达。也就是说,如果 Frame A 也是由 Frame B 的源头发送的话,那么 Frame A 永远应该早于 Frame B 抵达。另一方面,跨源头的顺序不会保证。 +3. 假设 [FCS](https://en.wikipedia.org/wiki/Frame_check_sequence) 在 transport 协议或者 MAC 层的每一跳中运用。但是对于恶意攻击的保护没有做任何假设。 + +实现在处理协议的过程中**也许**会"关闭"一个 transport 连接。当这种情况发生时,可以认为连接上没有更多的 frame 要发送,剩余的 frame 将会被忽略。 + +本文中描述的 RSocket 是基于 TCP、WebSocket、Aeron、和 [HTTP/2 streams](https://http2.github.io/http2-spec/#StreamsLayer) 作为 transport 协议来设计和测试的。 + +### 组帧 (Framing) 协议的用法 + +RSocket 所支持的 transport 协议中有些不支持保持消息边界的特定的组帧方式。对于这些协议,**必须**使用一个组帧协议来确保在 RSocket frame 的前面包含该 RSocket Frame 的长度。 + +如果 transport 协议保存了消息的边界,例如,提供了兼容组帧 (compatible framing),那么 Frame 长度字段**必须**忽略。但是,如果 transport 协议仅仅提供了一个 stream 的抽象,或者合并消息同时不保存边界,或者使用了多种 transport 协议,那么**必须**使用 frame 头。 + +| Transport 协议 | 是否需要 Frame 长度字段 | +| :------------ | :-------------- | +| TCP | __是__ | +| WebSocket | __否__ | +| Aeron | __否__ | +| HTTP/2 Stream | __是__ | + +### 组帧格式 + +当使用一个提供组帧能力的 transport 协议时,RSocket frame 被简单的封装在 transport 协议的消息体里。 + +``` + +-----------------------------------------------+ + | RSocket Frame ... + | + +-----------------------------------------------+ +``` + +当使用的 transport 协议不提供兼容的组帧能力,**必须**在 RSocket Frame 之前增加 Frame 长度字段。 + +``` + 0 1 2 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Frame Length | + +-----------------------------------------------+ + | RSocket Frame ... + | + +-----------------------------------------------+ +``` + +* __Frame 长度__: (24 位 = 最大值 16,777,215) 无符号 24-bit 整形,表示 Frame 的字节长度。不包含 Frame 长度字段本身。 + +__注意__: 字节序是 big endian。 + +### Frame 头的格式 + +RSocket frames 开头部分是 RSocket Frame 头。Frame 头的布局如下: + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |0| Stream ID | + +-----------+-+-+---------------+-------------------------------+ + |Frame Type |I|M| Flags | Depends on Frame Type ... + +-------------------------------+ +``` + +* __Stream ID__: (31 位 = 最大值 2^31-1 = 2,147,483,647) 无符号 31-bit 整数,代表当前帧所属的 stream 的标识,如果是 0,则代表整个连接。 + * 对于支持取消多路传输 (demultiplexing) 的协议,比如 HTTP/2,在所有参与方同意的前提下,Stream ID 字段可以忽略。也就是说,协商和同意的责任交给了 transport 协议这一层。 +* __Frame Type__: (6 位 = 最大值 63) Frame 类型。 +* __Flags__: (10 位) 当发送的 Frame 还没有被接收方解析时,任何与 frame 类型无关的 Flags 都应该被置为 0。一般来说,Flags 依赖 Frame 类型,但是所有的 frame 类型**必须**为以下的 flags 提供空间: + * (__I__)gnore: 对于不能理解的 Frame 可以忽略 + * (__M__)etadata: 有 Metadata 存在 + +__注意__: 字节序是 big endian。 + +#### 处理 Ignore 标记 + + (__I__)gnore 标记位被用来扩展协议。当标记位为 0 时表示协议不能忽略当前 frame。而当该标记位没有设置时,协议的实现**可能**在无法理解接受到的 Frame 时选择发送回一个 ERROR[CONNECTION_ERROR] 的 frame 并随后关闭底层的 transport 连接。 + +#### Frame 校验 + +RSocket 实现可能会在元数据层面为特定的 frame 提供自己的校验逻辑。但是,这个是应用应该关注的内容,而不是协议处理必须要做的。 + +#### 可选的元数据头 + +特定的 Frame 类型**可能**会包含元数据。如果该种 Frame 类型同时支持数据(Data)和元数据(Metadata),那么**必须**提供一个可选的元数据头。这个元数据头位于 Frame 头和 payload 之间。 + +元数据长度**必须**等于 Frame 长度减去 Frame 头的长度和 Frame Payload 的长度(如果有的话)。如果元数据长度不等于这个值的时候,这个 frame 就是非法的,接收方**必须**发送一个 ERROR[CONNECTION_ERROR] 的 frame 回应并关闭底层的 transport 连接,除非该 frame 的 IGNORE 标记位被设置。 + +一个包含数据和元数据的 frame: + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Metadata Length | + +---------------------------------------------------------------+ + | Metadata Payload ... + +---------------------------------------------------------------+ + | Payload of Frame ... + +---------------------------------------------------------------+ +``` + +一个容许包含数据和元数据的 frame,但是数据长度为 0: + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Metadata Length | + +---------------------------------------------------------------+ + | Metadata Payload ... + +---------------------------------------------------------------+ +``` + +如果一个 frame 只包含元数据,那么元数据长度的字段可以不提供: + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Metadata Payload ... + +---------------------------------------------------------------+ +``` + +* __元数据长度__: (24 位 = 最大值 16,777,215) 无符号 24-bit 整数,代表元数据的字节长度。排除了元数据长度字段本身的长度。 + + + +### Stream 标识 + +#### 生成 + +Stream ID 由请求方生成。Stream ID 的生命周期由请求类型和该类型上的 stream 语义来决定。 + +Stream ID 0 为保留值,用于任何涉及连接的操作。 + +一个 Stream ID 在一条连接上对于请求方来说**必须**是唯一的。 + +Stream ID 的生成遵循 [HTTP/2](https://tools.ietf.org/html/rfc7540) 中的做法,也就是,客户端**必须**用奇数作为 Stream ID,服务端**必须**使用偶数。 + +客户端的 Stream ID **必须**从 1 开始,然后以 2 为步长递增,比如:1,3,5,7 等等。 + +服务端的 Stream ID **必须**从 2 开始,然后以 2 为步长递增,比如:2,4,6,8 等等。 + +#### 生命周期 + +一旦最大的 Stream ID (2^31-1) 被使用,请求方**可能**会重用 Stream ID。回应方**必须**假设 Stream ID 是可重用的。 + +在最大的 Stream ID 被使用之后: + +1) 如果 Stream ID 重用没有激活: + +* 没有新的 stream 可以被创建,因此,在达到最大 ID 之后**必须**创建一条新的连接以便在其上可以重新创建新的 stream。 + +2) 如果 Stream ID 可以重用: + +* 请求方**必须**重用 ID,客户端从 1 开始,服务端从 2 开始,并以步长 2 递增。 +* 请求方**必须**跳过还在使用的 ID。 +* 如果回应方认为某个 ID 仍然在使用,**可能**选择回应 ERROR[REJECT]。然后请求方**可能**会用序列中的下一个它认为未被使用的 ID 重试。 +* 如果所有的 Stream ID 都正在使用,那么无法生成新的 stream。在这种情况下,**必须**建立一条新的连接以便在其上继续生成新的 stream。 + +**建议**当且仅当恢复特性(resumability)存在时才使用 Stream ID 的重用特性。 + +### Frame 类型 + +| 类型 | 值 | 描述 | +| :--------------------------------------- | :--- | :--------------------------------------- | +| __RESERVED__ | 0x00 | __Reserved__ | +| [__SETUP__](#frame-setup) | 0x01 | __Setup__: 由客户端发起一次协议的处理 | +| [__LEASE__](#frame-lease) | 0x02 | __Lease__: 有回应端发送,授予对端发送请求的权利 | +| [__KEEPALIVE__](#frame-keepalive) | 0x03 | __Keepalive__: 连接保活 | +| [__REQUEST_RESPONSE__](#frame-request-response) | 0x04 | __Request Response__: 请求一次回应 | +| [__REQUEST_FNF__](#frame-fnf) | 0x05 | __Fire And Forget__: 一次单向消息 | +| [__REQUEST_STREAM__](#frame-request-stream) | 0x06 | __Request Stream__: 请求一个有限的 (completable) stream | +| [__REQUEST_CHANNEL__](#frame-request-channel) | 0x07 | __Request Channel__: 请求一个有限的 (completable) 双向 stream | +| [__REQUEST_N__](#frame-request-n) | 0x08 | __Request N__: Reactive Streams 语义下的再请求 N 条 | +| [__CANCEL__](#frame-cancel) | 0x09 | __Cancel Request__: 取消请求 | +| [__PAYLOAD__](#frame-payload) | 0x0A | __Payload__: Stream 上的 Payload。例如:对应一个 request 的 response,或者 channel 中的消息 | +| [__ERROR__](#frame-error) | 0x0B | __Error__: 连接级别或者应用级别的错误 | +| [__METADATA_PUSH__](#frame-metadata-push) | 0x0C | __Metadata__: 异步元数据 frame | +| [__RESUME__](#frame-resume) | 0x0D | __Resume__: 替代 SETUP,恢复操作(可选) | +| [__RESUME_OK__](#frame-resume-ok) | 0x0E | __Resume OK__ : RESUME 的回应,当恢复操作可行的情况下发送 (可选) | +| [__EXT__](#frame-ext) | 0x3F | __Extension Header__: 用来扩展更多的 frame 类型以及更多的扩展 | + + + +### SETUP Frame (0x01) + +Setup frames **必须**始终使用 Stream ID 0,因为它们与连接相关。 + +客户端通过发送 SETUP frame 告知服务端它想以什么样的参数来操作。用法以及其所使用的消息序列可以参考[连接建立](#connection-establishment)。 + +连接上一个重要的参数和格式、布局、数据的 schema、以及 frame 的元信息有关。因为找不到更好的词来描述,可以借用一下 "MIME 类型" 来类比。实现**可能**借用典型的 MIME类型,也有**可能**使用自定义的类型来表示格式、布局、以及数据和元数据的 schema。协议的实现**一定不能**解析 MIME 类型,这个是应用才需要考虑的。 + +数据的编码格式和元数据的编码格式在 SETUP 中是分别存放的。 + +Frame 内容 + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Stream ID = 0 | + +-----------+-+-+-+-+-----------+-------------------------------+ + |Frame Type |0|M|R|L| Flags | + +-----------+-+-+-+-+-----------+-------------------------------+ + | Major Version | Minor Version | + +-------------------------------+-------------------------------+ + |0| Time Between KEEPALIVE Frames | + +---------------------------------------------------------------+ + |0| Max Lifetime | + +---------------------------------------------------------------+ + | Token Length | Resume Identification Token ... + +---------------+-----------------------------------------------+ + | MIME Length | Metadata Encoding MIME Type ... + +---------------+-----------------------------------------------+ + | MIME Length | Data Encoding MIME Type ... + +---------------+-----------------------------------------------+ + Metadata & Setup Payload +``` + +* __Frame Type__: (6 bits) 0x01 +* __Flags__: (10 bits) + * (__M__)etadata: 是否存在元数据 + * (__R__)esume Enable: 客户端请求可恢复。恢复身份标识存在 + * (__L__)ease: 是否遵从 LEASE +* __Major Version__: (16 位 = 最大值 65,535) 16位无符号整数,代表协议的主版本 +* __Minor Version__: (16 位 = 最大值 65,535) 16位无符号整数,代表协议的辅版本 +* __Time Between KEEPALIVE Frames__: (31 位 = 最大值 2^31-1 = 2,147,483,647) 31 位无符号整数,代表客户端发送两个 KEEPALIVE frame 之间的时间间隔(以毫秒为单位)。值**必须** > 0 + * 对于服务器-服务器之间的连接,一个合理的时间间隔是 500ms + * 对于移动端-服务器之间的连接,时间间隔往往应该 > 30,000ms +* __Max Lifetime__: (31 位 = 最大值 2^31-1 = 2,147,483,647) 31位无符号整数,代表客户端发送 KEEPALIVE 后等待服务器回应的最长等待时间(单位毫秒),超出等待时间可以认为服务器挂了。该值**必须** > 0 +* __Resume Identification Token Length__: (16 位 = 最大值 65,535) 16位无符号整数,代表恢复身份标识的字节长度。(如果 R 标记没有设置,该字段也不会存在) +* __Resume Identification Token__: 客户端用来恢复身份的标识(如果 R 标记没有设置,该字段也不会存在) +* __MIME Length__: MIME 类型的字节长度 +* __Encoding MIME Type__: 用来编码数据和元数据的 MIME 类型。它**应该**是一个包含在 [Internet media type](https://en.wikipedia.org/wiki/Internet_media_type) 中的符合 [RFC 2045](https://tools.ietf.org/html/rfc2045) 规范的 US-ASCII 字符串。其中许多类型在 [IANA](https://www.iana.org/assignments/media-types/media-types.xhtml) 注册,比如 [CBOR](https://www.iana.org/assignments/media-types/application/cbor)。 [Suffix](http://www.iana.org/assignments/media-type-structured-suffix/media-type-structured-suffix.xml) 规则**可能**被用来处理布局。例如,`application/x.netflix+cbor`、 + `application/x.reactivesocket+cbor` 或 `application/x.netflix+json`。这个字符串**必须不能**以 null 作为结束符。 +* __Setup Data__: 包含描述Setup 头的发送方的连接能力的 payload。 + +__注意__: 如果服务器接受到了一个设置了 (__R__)esume Enabled 的 SETUP frame,但是不支持恢复操作,**必须**拒绝该 SETUP 请求并以 ERROR[REJECTED_SETUP] 回应。 + + + +### ERROR Frame (0x0B) + +当某个 request/stream 发生错误时,或者连接发生错误,或者回应 SETUP frame 时,都可以使用 Error frame。 + +Frame 内容 + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Stream ID | + +-----------+-+-+---------------+-------------------------------+ + |Frame Type |0|0| Flags | + +-----------+-+-+---------------+-------------------------------+ + | Error Code | + +---------------------------------------------------------------+ + Error Data +``` + +* __Frame Type__: (6 位) 0x0B +* __Error Code__: (32 位 = 最大值 2^31-1 = 2,147,483,647) 错误类型。 + * 参见下面合法的错误码。 +* __Error Data__: 包含描述错误信息的 Payload。Error Data **应该**是一个 UTF-8 编码的字符串,并且不能以 null 结尾 + +Stream ID 为 0 表示错误与连接有关,包括连接的建立。Stream ID > 0 表示错误与某个特定的 stream 相关。 + +Error Data 通常是 Exception 消息,但是也可以包含 stacktrace 信息,如果合适的话。 + +#### 错误码 + +| 类型 | 值 | 描述 | +| :-------------------- | :--------- | :--------------------------------------- | +| __RESERVED__ | 0x00000000 | __Reserved__ | +| __INVALID_SETUP__ | 0x00000001 | 服务器认为 Setup frame 非法(可能是因为客户端太新而服务器太老). Stream ID **必须**为 0 | +| __UNSUPPORTED_SETUP__ | 0x00000002 | 服务器不支持部分(或者全部)客户端指定的参数。Stream ID **必须**为 0 | +| __REJECTED_SETUP__ | 0x00000003 | 服务器拒绝 setup,并可以在 payload 中包含拒绝的理由。Stream ID **必须**为 0 | +| __REJECTED_RESUME__ | 0x00000004 | 服务器拒绝 resume,并可以在 payload 中包含拒绝的理由。Stream ID **必须**为 0 | +| __CONNECTION_ERROR__ | 0x00000101 | 连接即将被终止。Stream ID **必须**为 0。该 frame 的发送方和接受方**可能**会立即关闭连接,而不会等待其中的 stream 结束。 | +| __CONNECTION_CLOSE__ | 0x00000102 | 连接即将被终止。Stream ID **必须**为 0。该 frame 的发送方和接受方**必须**等待连接上的 stream 处理完毕后才能关闭该连接。新的请求**可能**不被接受。 | +| __APPLICATION_ERROR__ | 0x00000201 | 应用层逻辑,产生 Reactive Streams 中的 _onError_ 事件。Stream ID **必须** > 0 | +| __REJECTED__ | 0x00000202 | 回应方选择拒绝请求,即使请求合法。回应方需要确保这个请求没有被处理过。拒绝的理由在 Error Data 章节详细阐述。Stream ID **必须** > 0 | +| __CANCELED__ | 0x00000203 | 回应方取消请求,可能已经开始处理该请求(与 REJECTED 类似,但是不保证没有副作用)。Stream ID **必须** > 0 | +| __INVALID__ | 0x00000204 | 非法请求。Stream ID **必须** > 0 | +| __RESERVED__ | 0xFFFFFFFF | __保留,扩展字段__ | + +__注意__: 0x0001 - 0x00300 之间还未使用的值作为协议未来的扩展保留。0x00301 - 0xFFFFFFFE 预留,用作应用层的错误。 + +在本文中,当提及某个特定错误码的帧的表示时,使用这种形式来表达:ERROR[error_code] 或 ERROR[error_code|error_code] + +例如: + +* ERROR[INVALID_SETUP] 表示 INVALID_SETUP 的 ERROR frame +* ERROR[REJECTED] 代表 REJECTED 的 ERROR frame +* ERROR[CONNECTION_ERROR|REJECTED_RESUME] 代表 CONNECTION_ERROR 或者 REJECTED_RESUME 的 ERROR frame + + + +### LEASE Frame (0x02) + +Lease frame **可能**由来自客户端或者服务器端的回应方发送,用来通知请求方可以发送请求的时长,以及在这段时间窗口之内可以发送的数量。详见 [租约场景](#lease-semantics)。 + +最近收到的 LEASE frame 覆盖以往所有的 LEASE frame。 + +Lease frame **必须**使用 Stream ID 0,因为其与连接有关。 + +Frame 内容 + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Stream ID = 0 | + +-----------+-+-+---------------+-------------------------------+ + |Frame Type |0|M| Flags | + +-----------+-+-+---------------+-------------------------------+ + |0| Time-To-Live | + +---------------------------------------------------------------+ + |0| Number of Requests | + +---------------------------------------------------------------+ + Metadata +``` + +* __Frame Type__: (6 位) 0x02 +* __Flags__: (10 位) + * (__M__)etadata: 存在元数据 +* __Time-To-Live (TTL)__: (31 位 = 最大值 2^31-1 = 2,147,483,647) 31位无符号整数,表示自收到 LEASE 起有效的租约时间(单位毫秒)。值**必须** > 0 +* __Number of Requests__: (31 位 = 最大值 2^31-1 = 2,147,483,647) 31位无符号整数,表示在收到下一个 LEASE 之前可以发送的请求数。值**必须** > 0 + +回应方的实现**可能**会通过发送一个 __Number of Requests__ 或者 __Time-To-Live__ 为 0 的 LEASE 来终止后续的请求。 + +当一个 LEASE 到期,请求方可以继续发送请求数为 0。 + +该 frame 只支持包含元信息,所以元信息的长度**必须不能**包括,即使是 (M)etadata 标志位被设置为 true。 + + + +### KEEPALIVE Frame (0x03) + +KEEPALIVE frame **必须**永远使用 Stream ID 0,因为其与连接相关。 + +KEEPALIVE frame **必须**由客户端发起,设置 (__R__)espond 标志位,并周期性的发送。 + +KEEPALIVE frame **可能**由服务器端发起,设置 (__R__)espond 标志位,并在收到应用请求后发送。 + +在接收到 (__R__)espond 标志位被设置的 KEEPALIVE frame 之后,客户端或者服务器端**必须**回应一个 (__R__)espond flag **没有**设置的 KEEPALIVE。接受的 KEEPALIVE 中的数据**必须**与生成的 KEEPALIVE 一致。 + +服务器端收到 KEEPALIVE 表明客户端存活。 + +客户端收到 KEEPALIVE 说明服务器端存活。 + +Frame 内容 + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Stream ID = 0 | + +-----------+-+-+-+-------------+-------------------------------+ + |Frame Type |0|0|R| Flags | + +-----------+-+-+-+-------------+-------------------------------+ + |0| Last Received Position | + + + + | | + +---------------------------------------------------------------+ + Data +``` + +* __Frame Type__: (6 位) 0x03 +* __Flags__: (10 位) + * (__R__)espond:是否回应 KEEPALIVE +* __Last Received Position__: (63 位 = 最大值 2^63-1) 63 位无符号长整形,表示恢复到上次接收的位置。值**必须** > 0。(可选。如不支持,全部置空) +* __Data__: 关联在 KEEPALIVE 上的数据 + + + +### REQUEST_RESPONSE Frame (0x04) + +Frame 内容 + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Stream ID | + +-----------+-+-+-+-------------+-------------------------------+ + |Frame Type |0|M|F| Flags | + +-------------------------------+ + Metadata & Request Data +``` + +* __Frame Type__: (6 位) 0x04 +* __Flags__: (10 位) + * (__M__)etadata: 存在元数据 + * (__F__)ollows: 后续还有更多分片 (fragment) +* __Request Data__: 被请求的服务标识,以及请求参数。 + + + +### REQUEST_FNF (Fire-n-Forget) Frame (0x05) + +Frame 内容 + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Stream ID | + +-----------+-+-+-+-------------+-------------------------------+ + |Frame Type |0|M|F| Flags | + +-------------------------------+ + Metadata & Request Data +``` + +* __Frame Type__: (6 位) 0x05 +* __Flags__: (10 位) + * (__M__)etadata: 存在元数据 + * (__F__)ollows: 后续还有更多分片 (fragment) +* __Request Data__: 被请求的服务标识,以及请求参数。 + + + +### REQUEST_STREAM Frame (0x06) + +Frame 内容 + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Stream ID | + +-----------+-+-+-+-------------+-------------------------------+ + |Frame Type |0|M|F| Flags | + +-------------------------------+-------------------------------+ + |0| Initial Request N | + +---------------------------------------------------------------+ + Metadata & Request Data +``` + +* __Frame Type__: (6 位) 0x06 +* __Flags__: (10 位) + * (__M__)etadata: 存在元数据 + * (__F__)ollows: 后续还有更多分片 (fragment) +* __Initial Request N__: (31 位 = 最大值 2^31-1 = 2,147,483,647) 31位无符号整数,代表初始的请求条目。值**必须** > 0 +* __Request Data__: 被请求的服务标识,以及请求参数。 + +参阅 [流量控制:Reactive Streams 语义](#flow-control-reactive-streams) 了解有关 RequestN 行为的更多信息。 + + + +### REQUEST_CHANNEL Frame (0x07) + +Frame 内容 + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Stream ID | + +-----------+-+-+-+-+-----------+-------------------------------+ + |Frame Type |0|M|F|C| Flags | + +-------------------------------+-------------------------------+ + |0| Initial Request N | + +---------------------------------------------------------------+ + Metadata & Request Data +``` + +* __Frame Type__: (6 位) 0x07 +* __Flags__: (10 位) + * (__M__)etadata: 存在元数据 + * (__F__)ollows: 后续还有更多分片 (fragment) + * (__C__)omplete: 代表 stream 结束的标志位 + * 如果设置,Subscriber/Observer 上的`onComplete()`或者对等的方法会被调用。 +* __Initial Request N__: (31 位 = 最大值 2^31-1 = 2,147,483,647) 31位无符号整数,代表 channel 上初始的 request N 的值。值**必须** > 0。 +* __Request Data__: 被请求的服务标识,以及请求参数。 + +A requester MUST send only __one__ REQUEST_CHANNEL frame. Subsequent messages from requester to responder MUST be sent as PAYLOAD frames. + +请求方**必须**只发送**一个** REQUEST_CHANNEL frame。请求方后续发给回应方的消息**必须**是 PAYLOAD frame。 + +请求方在发送 REQUEST_CHANNEL frame 之后,**不能**继续发送 PAYLOAD frame,直到响应方回应 REQUEST_N frame 授信指定数目的 PAYLOAD 可以继续发送为止。 + +参阅 [流量控制:Reactive Streams 语义](#flow-control-reactive-streams) 了解有关 RequestN 行为的更多信息。 + + + +### REQUEST_N Frame (0x08) + +Frame 内容 + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Stream ID | + +-----------+-+-+---------------+-------------------------------+ + |Frame Type |0|0| Flags | + +-------------------------------+-------------------------------+ + |0| Request N | + +---------------------------------------------------------------+ +``` + +* __Frame Type__: (6 位) 0x08 +* __Request N__: (31 位 = 最大值 2^31-1 = 2,147,483,647) 31位无符号整数,代表请求回应的条目。值**必须** > 0。 + +参阅 [流量控制:Reactive Streams 语义](#flow-control-reactive-streams) 了解有关 RequestN 行为的更多信息。 + + + +### CANCEL Frame (0x09) + +Frame 内容 + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Stream ID | + +-----------+-+-+---------------+-------------------------------+ + |Frame Type |0|0| Flags | + +-------------------------------+-------------------------------+ +``` + +* __Frame Type__: (6 位) 0x09 + + + +### PAYLOAD Frame (0x0A) + +Frame 内容 + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Stream ID | + +-----------+-+-+-+-+-+---------+-------------------------------+ + |Frame Type |0|M|F|C|N| Flags | + +-------------------------------+-------------------------------+ + Metadata & Data +``` + +* __Frame Type__: (6 位) 0x0A +* __Flags__: (10 位) + * (__M__)etadata: 存在元信息 + * (__F__)ollows: 后续还有更多分片 (fragment) + * (__C__)omplete: 代表 stream 结束的标志位 + * 如果设置,Subscriber/Observer 上的`onComplete()`或者对等的方法会被调用。 + * (__N__)ext: 代表 Next 的标志位(还存在 Payload 数据,或者元数据,或者二者皆有) + * 如果设置,Subscriber/Observer 上的 `onNext(Payload)` 或者对等的方法会被调用。 +* __Payload Data__: Reactive Streams onNext 的 payload 数据。 + + (C)omplete 和 (N)ext 合法的组合包括: + +* (C)omplete 和 (N)ext 同时设置表示 PAYLOAD 既包含数据也代表 stream 的结束。 + * 例如:一个 Observable stream 接收了 `onNext(payload)` 然后紧接着又接收了 `onComplete()`。 +* 只有 (C)omplete 设置,表示 PAYLOAD 不包含数据,仅仅表示 stream 结束。 + * 例如:一个 Observable stream 接收了 `onComplete()`。 +* 只有 (N)ext 设置,表示 PAYLOAD 包含数据但是 stream **没有**结束。 + * 例如:一个 Observable stream 接收了`onNext(payload)`。 + +一个 PAYLOAD **不应该**存在 (C)complete 和 (N)ext 同时为空 (false) 的情况。 + +引入 (N)ext 标记位,而不是沿用数据长度(> 0)的理由是,长度为 0 的数据是合法的 PAYLOAD,递交给应用层的 PAYLOAD 中包含 0 字节的数据。 + +例如:一个 Observable stream 通过 `onNext(payload)` 接收到了 0 字节长度的数据。 + + + +### METADATA_PUSH Frame (0x0C) + +请求方或者回应方可以使用 Metadata Push frame 来异步发送元信息通知给它的对端。 + +METADATA_PUSH frames **必须**始终使用 Stream ID 0,因为与连接相关。 + +捆绑在某个 stream 上的元信息使用的是自有的 Payload frame 元信息标识。 + +Frame 内容 + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Stream ID = 0 | + +-----------+-+-+---------------+-------------------------------+ + |Frame Type |0|1| Flags | + +-------------------------------+-------------------------------+ + Metadata +``` + +* __Frame Type__: (6 位) 0x0C + +这个 frame 仅支持元数据,因此**不能**包含元数据长度头。 + + + +### EXT (Extension) Frame (0x3F) + +扩展 frame 的通用格式如下所示。 + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Stream ID | + +-----------+-+-+---------------+-------------------------------+ + |Frame Type |I|M| Flags | + +-------------------------------+-------------------------------+ + |0| Extended Type | + +---------------------------------------------------------------+ + Depends on Extended Type... +``` + +* __Frame Type__: (6 位) 0x3F +* __Flags__: (10 位) + * (__I__)gnore: 如果不能解析 frame 是否可以忽略? + * (__M__)etadata: 存在元信息 +* __Extended Type__: (31 位 = 最大值 2^31-1 = 2,147,483,647) 31位无符号整数,表示扩展类型的信息。值**必须** > 0。 + +## 恢复操作 + +由于在 RSocket 中有大量活跃请求存在的事实,在传输失败时提供可恢复的能力是很有必要的。当然,支持与否完全取决于协议的实现。 + +### 假设 + +RSocket 恢复特性仅适用于特定的场景,并不是一个普适 ("always works") 的方案。当恢复操作不可行的情况下,连接应当被终止,并按照协议规定以一个 ERROR 回应对端。 + +1. 恢复是一个可选实现,但是强烈建议实现。客户端和服务器端应当假设协议**没有**恢复的能力。 +2. 恢复是一个优化操作。不能保证该操作永远能成功。 +3. 恢复操作应当是快速的,只需要最小集合的状态交换。 +4. 恢复是针对失联场景设计的特性,并假设客户端状态和服务器状态在连接断开的情况下也会维持,也就是,假定双方的状态不会丢失。这是一个非常重要的假设,否则,就需要依赖可靠消息传递 ("guarenteed messaging") 的存在。 +5. 恢复假设 Lease,数据格式(编码) 等在操作过程中不会变化。例如:在恢复操作的过程中,客户端不容许改变元信息的 MIME 类型或者数据的 MIME 类型或者版本等信息。 +6. 恢复操作永远由客户端发起,服务端可以选择接受或者拒绝。 +7. 恢复操作对于发送 frame 的应用状态不做原子性、事务性方面的假设。参见上文。 + +### 隐式位置 + +恢复操作需要知道上一次连接上数据接收到了哪个地方。处于简化的目的,假定底层的传输层支持帧级别上的连续传输 (contiguous delivery)。换句话说,不容许发送帧的一部分,帧与帧之间不能有空隙。TCP、WebSocket、和 Aeron 都是满足要求的传输层协议。特别的,在 TCP 上需要额外的工作就可以满足要求。 + +当请求方或者回应方**发送** REQUEST、CANCEL、或者 PAYLOAD frame 时,会维护一个这个 frame 在连接上发送方向的**位置**。这是一个从 0 开始计数的 64位整数。当请求方或者回应方**接收到** REQUEST、CANCEL、或者 PAYLOAD frame 时,会维护一个这个 frame 在连接上接收方向的**隐式位置**。这也是一个从 0 开始计数的 64位整数。 + +之所以称之为 "隐式" 的原因是每个 frame 中并不包含位置信息,位置是根据连接上发送/接收消息相互关系推断出来的。 + +该位置用来定位恢复操作开始的位置。 + +除 REQUEST、CANCEL、ERROR、和 PAYLOAD,其他 frame 没有指派的(或者隐式的)位置。 + +当客户端发送一个 RESUME frame 时,会发送两个隐式位置:最近一个接受到的 frame;仍然保存的最早的 frame 位置。然后服务器端根据该信息决定恢复是否可能:客户端最近接收的 frame 之后的所有 frame 在服务器端是否还存在?服务器端保存的位置之后的 frame 在客户端是否还保留。如果恢复是可行的,服务器端会回应一个 RESUME_OK,其中标明它上次接收到的位置。 + +### 客户端生命管理 + +服务器对客户端的生命管理**必须**客户端为了成功恢复重建连接的时间(应该翻译的不对…)。客户端生命管理的手段完全由实现决定。 + +### 恢复操作 + +所有 ERROR frame 必须发送 CONNECTION_ERROR 或者 REJECTED_RESUME 的错误码。 + +客户端恢复操作发生的时机是,客户端全新建立一条连接并试图恢复。操作如下: + +* 客户端发送 RESUME frame。其后客户端**禁止**发送其它类型的 frame,直到恢复完成。RESUME 中的身份标识**必须**与原始的 SETUP frame 中的标识一致。RESUME 中最近接收位置的字段**必须**是服务器最新成功接收的隐性位置。 +* 客户端等待来自服务器端的回应,可能是 RESUME_OK 或者 ERROR[CONNECTION_ERROR|REJECTED_RESUME] frame。 +* 接收到 ERROR[REJECTED_RESUME] frame 之后,客户端**严禁**重复发起恢复请求。 +* 接收到 RESUME_OK,客户端: + * **必须**假定后续的 REQUEST、CANCEL、ERROR、PAYLOAD frames 的隐性位置从上一次隐性位置开始 + * **可能**根据服务端回应的 RESUME_OK 中的最新接收位置开始重新传输*所有的* REQUEST、CANCEL、ERROR、和 PAYLOAD frames + * **可能**发送一个 ERROR[CONNECTION_ERROR|CONNECTION_CLOSE] frame 表明连接结束,之后**严禁**再次发起恢复 + +服务器端在接收到客户端发送的 RESUME frame 后开始恢复操作。操作如下: + +* 接收到 RESUME frame 后,服务器: + * 如果不支持恢复操作,**必须**发送一个 ERROR[REJECTED_RESUME] frame + * 通过 RESUME 中的身份标识字段决定为哪一个客户端恢复。正确识别客户端身份后,**可以**继续恢复。如果不能识别,那么服务器**必须**发送 ERROR[REJECTED_RESUME] frame 回应。 + * if successfully identified, then the server MAY send a RESUME_OK and then: + * 如果正确识别,服务器**可以**发送 RESUME_OK 回应,并: + * **必须**假定后续的 REQUEST、CANCEL、ERROR、PAYLOAD frames 的隐性位置从上一次隐性位置开始 + * **可能**根据客户端发送的 RESUME 中的最新接收位置开始重新传输*所有的* REQUEST、CANCEL、ERROR、和 PAYLOAD frames + * 正确识别后,如果服务器无法(无法恢复的位置,或者其他原因)从 RESUME 中的最近接收位置字段处恢复,服务器**也可能**发送一个 ERROR[REJECTED_RESUME] frame。 + +服务器端在接收到 SETUP frame 之后又接收到了一个 RESUME frame,**应当**发送 ERROR[CONNECTION_ERROR] 作为回应。 + +服务器端在接收到 RESUME frame 之后又接收到了 RESUME frame,**应当**发送 ERROR[CONNECTION_ERROR] 作为回应。 + +在恢复的场景中**不应当**遵从上条连接上的租约语义,**必须**遵从服务器端在新的连接上发送过来的新的租约语义。 + + + +#### RESUME Frame (0x0D) + +Resume frame 的格式如下。 + +RESUME frames **必须**始终使用 Stream ID 0,因为与连接相关。 + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Stream ID = 0 | + +-----------+-+-+---------------+-------------------------------+ + |Frame Type |0|0| Flags | + +-------------------------------+-------------------------------+ + | Major Version | Minor Version | + +-------------------------------+-------------------------------+ + | Token Length | Resume Identification Token ... + +---------------------------------------------------------------+ + |0| | + + Last Received Server Position + + | | + +---------------------------------------------------------------+ + |0| | + + First Available Client Position + + | | + +---------------------------------------------------------------+ +``` + +* __Frame Type__: (6 位) 0x0D +* __Major Version__: (16 位 = 最大值 65,535) 16位无符号整数,表示协议的主版本。 +* __Minor Version__: (16 位 = 最大值 65,535) 16位无符号整数,表示协议的辅版本。 +* __Resume Identification Token Length__: (16 位 = 最大值 65,535) 16位无符号整数,恢复身份标识的字节长度。 +* __Resume Identification Token__: 用来恢复身份的标识。与 SETUP 中的身份标识相同。 +* __Last Received Server Position__: (63 位 = 最大值 2^63-1) 63位无符号长整形,表示客户端最近接收到的隐形位置。 +* __First Available Client Position__: (63 位 = 最大值 2^63-1) 63位无符号长整形,表示客户端可以重放 frame 的最早位置 + + + +#### RESUME_OK Frame (0x0E) + +Resume OK frame 格式如下。 + +RESUME OK frames **必须**始终使用 Stream ID 0,因为与连接相关。 + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Stream ID = 0 | + +-----------+-+-+---------------+-------------------------------+ + |Frame Type |0|0| Flags | + +-------------------------------+-------------------------------+ + |0| | + + Last Received Client Position + + | | + +---------------------------------------------------------------+ +``` + +* __Frame Type__: (6 位) 0x0E +* __Last Received Client Position__: (63 位 = 最大值 2^63-1) 63位无符号长整型,表示服务端接收客户端数据最新的隐式位置 + +#### Keepalive 位置字段 + +Keepalive frame 包含客户端(或者服务器端)的隐式位置。目的是让对端知晓自己的位置。 + +这个信息**可能**被用在重新传输的场景里更新状态,比如:缩短用作重新传输的缓冲区大小,或者 stream 状态之间可能的关联 。 + +### 身份标识的处理 + +对于恢复身份标识的具体要求由协议的实现决定。但是,以下是实现上的一些建议和指南: + +* 标识可以由客户端产生 +* 标识也可以由客户端和服务器端之外的系统产生,并独立管理 +* 标识用来唯一标识在服务器上一条连接。服务器不应该对标识生成的方法做任何假设,并应该认为标识是不透明的。这样做确保了客户端可以和任何支持恢复操作的 RSocket 实现相兼容,并容许客户端对身份标识的生成完全控制 +* 在每一个 RSocket 的生命周期中(包含可能出现的恢复阶段),标识**必须**保持有效 +* 服务器不应该接受一个包含已经在使用的标识的 SETUP 请求 +* 标识应当可以防御重放攻击,因此,标识的有效性只应该与特定连接的生命周期相关 +* 标识不应该被第三方的攻击软件猜测到 + +## 连接建立 + +__注意__: 语义与 [TLS False Start](https://tools.ietf.org/html/draft-bmoeller-tls-falsestart-00) 类似 + +成功建立连接之后,客户端**必须**发送包含 Stream ID 为 0 的 SETUP 或者 RESUME frame。如果接收到了不是 SETUP|RESUME 的 frame 或者 SETUP|RESUME frame 中的 Stream ID > 0,服务器**必须**发送 ERROR[INVALID_SETUP] 作为回应,并关闭连接。 + +关于恢复更多的信息参阅[恢复操作](#resume-operation)。本章节后续内容假设使用 SETUP 建立一个连接。 + +客户端的请求方可以知会服务端的回应方它是否根据 SETUP frame 中 __L__ 标识的存在来遵循 LEASE。 + +客户端的请求方,如果**没有**在 SETUP frame 中设置 __L__ 标识的话,可能不会等待服务器端的 LEASE 回应,而立即发送请求。 + +客户端的请求方,如果在 SETUP frame 中设置 __L__ 标识的话,**必须**等待服务器端的回应方回应一个 LEASE frame 之后才能发请求。 + +如果服务器接受了 SETUP frame 的内容,并且其中设置了 __L__ 标识的话,它**必须**发送一个 LEASE frame 回应。服务器端的请求方可能在收到 SETUP frame 时立即发送请求,如果它容许 __L__ 标识可以不在 SETUP frame 中设置的话。 + +如果服务器**不接受** SETUP frame 中的内容,服务器**必须**发送 ERROR[INVALID_SETUP|UNSUPPORTED_SETUP] 作为回应,并关闭连接。 + +服务器端的请求方镜像了客户端的请求方的 LEASE 请求。如果服务器端的请求方在 SETUP frame 里设置了 __L__ 标志,那么服务器端的请求方**必须**等待客户端的回应方发送的 LEASE frame 后才能发送请求。客户端的回应方**必须**在接收到一个 __L__ 标志被设置的 SETUP frame 之后回应一个 LEASE frame。 + +当客户端接收到了一个请求对应的回应,一个 LEASE frame,或者看到了一个 REQUEST 类型,就认为 SETUP 已被接受。 + +当客户端接收到一个 ERROR 就认为 SETUP 被拒绝。 + +在连接建立完成之前,请求方**禁止**发送任何请求 frame。 + +在连接建立完成之前,回应方**禁止**回应任何 PAYLOAD frame。 + +### 协商 + +协议假定客户端会支配服务器要做什么。服务器决定是(接受)否(拒绝)支持发送过来的 SETUP frame。ERROR[INVALID_SETUP|UNSUPPORTED_SETUP|REJECTED_SETUP] 用来表明拒绝的原因。 + +### 不带 LEASE 的序列 + +不带 LEASE 的序列如下所示: + +1. 客户端请求,服务器端**接受** SETUP + * 客户端建连 & 发送 SETUP & 发送 REQUEST + * 服务器接受 SETUP,处理 REQUEST,根据 REQUEST 类型做正常的回应 +2. 客户端请求,服务器端**拒绝** SETUP + * 客户端建连 & 发送 SETUP & 发送 REQUEST + * 服务器拒绝 SETUP,回应 ERROR[INVALID_SETUP|UNSUPPORTED_SETUP|REJECTED_SETUP],关闭连接 +3. 服务器端请求,服务器端**接受** SETUP + * 客户端建连 & 发送 SETUP + * 服务器接受 SETUP,回应 REQUEST 类型 +4. 服务器端请求,服务器端**拒绝** SETUP + * 客户端建连 & 发送 SETUP + * 服务器拒绝 SETUP,回应 ERROR[INVALID_SETUP|UNSUPPORTED_SETUP|REJECTED_SETUP],关闭连接 + +### 带 LEASE 的序列 + +带 LEASE 的序列如下所示: + +1. 客户端请求,服务器端**接受** SETUP + * 客户端建连 & 发送带有__L__标识的 SETUP + * 服务器接受 SETUP,回应 LEASE frame + * 客户端发送 REQUEST +2. 客户端请求,服务器端**拒绝** SETUP + * 客户端建连 & 发送带有__L__标识的 SETUP + * 服务器拒绝 SETUP,回应 ERROR[INVALID_SETUP|UNSUPPORTED_SETUP|REJECTED_SETUP],关闭连接 +3. 服务器端请求,服务器端**接受** SETUP + * 客户端建连 & 发送带有__L__标识的 SETUP + * 服务器接受 SETUP,回应 LEASE frame + * 客户端发送 LEASE frame + * 服务器发送 REQUEST +4. 服务器端请求,服务器端**拒绝** SETUP + * 客户端建连 & 发送带有__L__标识的 SETUP + * 服务器拒绝 SETUP,回应 ERROR[INVALID_SETUP|UNSUPPORTED_SETUP|REJECTED_SETUP],关闭连接 + +## 分段和重组 + +PAYLOAD frame 和所有的 REQUST frame 可能代表一个大对象,因此可能需要分段以符合 Frame 中的数据尺寸。在这种情况发生时,可以用 __F__ 标志位来表示当前 frame 后是否还有更多的分段。 + +分段不会改签 request(n) 或者 lease 数目的语义。换句话说,分了段的 PAYLOAD frame 等同于一个 request(n),一个 request 等同于一个 lease 数目,所以与一个 frame 被分成多少段无关。 + +#### PAYLOAD Frame + +当一个 PAYLOAD frame 需要分段时,将会使用 (F)ollows 标志位来传输一系列的 PAYLOAD frame。 + +当一个 PAYLOAD 分段时,元数据**必须**在数据之前完全传输。 + +举例,一个有 20MB 元数据和 25M 数据的 PAYLOAD 被分段成 3 个 frame: + +``` +-- PAYLOAD frame 1 +Frame length = 16MB +(M)etadata present = 1 +(F)ollows = 1 (fragments coming) +Metadata Length = 16MB + +16MB of METADATA +0MB of Data + +-- PAYLOAD frame 2 +Frame length = 16MB +(M)etadata present = 1 +(F)ollows = 1 (fragments coming) +Metadata Length = 4MB + +4MB of METADATA +12MB of Data + +-- PAYLOAD frame 3 +Frame length = 13MB +(M)etadata present = 0 +(F)ollows = 0 + +0MB of METADATA +13MB of Data +``` + +如果发送方(请求方或者回应方)要取消一个分段序列的发送,它**可能**会在分段传输完成之前就发送 CANCEL frame。 + +#### REQUEST Frames + +当 REQUEST_RESPONSE、REQUEST_FNF、REQUEST_STREAM、或 REQUEST_CHANNEL 需要分段的时候,第一个 frame 是带有 (F)ollows 标识的 REQUEST_* frame,后续是一系列的 PAYLOAD frame。 + +分段时,元数据**必须**在数据之前完全传输。 + +举例,一个有 20MB 元数据和 25M 数据的 PAYLOAD 被分段成 3 个 frame: + +``` +-- REQUEST_RESPONSE frame 1 +Frame length = 16MB +(M)etadata present = 1 +(F)ollows = 1 (fragments coming) +Metadata Length = 16MB + +16MB of METADATA +0MB of Data + +-- PAYLOAD frame 2 +Frame length = 16MB +(M)etadata present = 1 +(F)ollows = 1 (fragments coming) +Metadata Length = 4MB + +4MB of METADATA +12MB of Data + +-- PAYLOAD frame 3 +Frame length = 13MB +(M)etadata present = 0 +(F)ollows = 0 + +0MB of METADATA +13MB of Data +``` + +如果请求方要取消一个分段序列的发送,它**可能**会在分段传输完成之前就发送 CANCEL frame。 + +## Stream 序列和存活时间 + +Stream 存活一段特定的时间。因此协议的实现可以假设 Steam ID 仅在有限的时间段内有效。这个时间段受限于: a) 底层传输协议连接的存活时间,或 b) 当使用可恢复性跨连接延长会话寿命的情况下,会话的存活时间。在此之上,每一种交互模式都会影响到存活时间,取决于请求方和回应方的交互序列。 + +在下面的章节中,"RQ -> RS" 代表请求方发送一个 frame 给回应方。"RS -> RQ" 表示回应方发送一个 frame 给请求方。 + +在下面的章节中,"*" 代表 0 或者更多,"+" 表示 1 或者更多。 + +一旦 stream "终止",请求方和回应方就可以"遗忘"对应的 Stream ID,但是**严禁**重用该 Stream ID。更多信息参见 [Stream Identifier](#stream-identifiers)。 + + + +### Request Response + +1. RQ -> RS: REQUEST_RESPONSE +2. RS -> RQ: PAYLOAD with COMPLETE + +或者 + +1. RQ -> RS: REQUEST_RESPONSE +2. RS -> RQ: ERROR[APPLICATION_ERROR|REJECTED|CANCELED|INVALID] + +或者 + +1. RQ -> RS: REQUEST_RESPONSE +2. RQ -> RS: CANCEL + +自回应 response 起,stream 在接收方终止。 + +自接收到 CANCEL 起,stream 在接收方终止,**不应该**发送 response。 + +自发送完 CANCEL 起,stream 在请求方终止。 + +自接收到 COMPLETE 或 ERROR[APPLICATION_ERROR|REJECTED|CANCELED|INVALID] 起,stream 在请求方终止。 + + + +### Request Fire-n-Forget + +1. RQ -> RS: REQUEST_FNF + +Upon reception, the stream is terminated by the Responder. + +自接收起,stream 在接收方终止。 + +自发送起,stream 在请求方终止。 + +假定 REQUEST_FNF 会尽可能被处理,但是**也有可能**不会:(1) SETUP 被拒绝,(2) 格式错误 (3) 等等 + + + +### Request Stream + +1. RQ -> RS: REQUEST_STREAM +2. RS -> RQ: PAYLOAD* +3. RS -> RQ: ERROR[APPLICATION_ERROR|REJECTED|CANCELED|INVALID] + +或者 + +1. RQ -> RS: REQUEST_STREAM +2. RS -> RQ: PAYLOAD* +3. RS -> RQ: COMPLETE + +或者 + +1. RQ -> RS: REQUEST_STREAM +2. RS -> RQ: PAYLOAD* +3. RQ -> RS: CANCEL + +请求方在任何时候都可能发送 REQUEST_N frame。 + +自接收到 CANCEL 起,stream 在接收方终止。 + +自发送完 CANCEL 起,stream 在请求方终止。 + +自接收到 COMPLETE 或 ERROR[APPLICATION_ERROR|REJECTED|CANCELED|INVALID] 起,stream 在请求方终止。 + +自发送完 COMPLETE 或 ERROR[APPLICATION_ERROR|REJECTED|CANCELED|INVALID] 起,stream 在接收方终止。 + + + +### Request Channel + +#### 请求方和回应方的 COMPLETE 共存 + +1. RQ -> RS: REQUEST_CHANNEL + +2. RQ -> RS: PAYLOAD* + +3. RQ -> RS: COMPLETE + + 同时存在 + +4. RS -> RQ: PAYLOAD* + +5. RS -> RQ: COMPLETE + +#### 请求方的错误,回应方终止 + +1. RQ -> RS: REQUEST_CHANNEL + +2. RQ -> RS: PAYLOAD* + +3. RQ -> RS: ERROR[APPLICATION_ERROR] + + 同时存在 + +4. RS -> RQ: PAYLOAD* + +#### 请求方的错误,回应方已经完成 + +1. RQ -> RS: REQUEST_CHANNEL + +2. RQ -> RS: PAYLOAD* + +3. RQ -> RS: ERROR[APPLICATION_ERROR] + + 同时存在 + +4. RS -> RQ: PAYLOAD* + +5. RS -> RQ: COMPLETE + +#### 回应方错误,请求方终止 + +1. RQ -> RS: REQUEST_CHANNEL + +2. RQ -> RS: PAYLOAD* + + 同时存在 + +3. RS -> RQ: PAYLOAD* + +4. RS -> RQ: ERROR[APPLICATION_ERROR|REJECTED|CANCELED|INVALID] + +#### 回应方错误,请求方已经完成 + +1. RQ -> RS: REQUEST_CHANNEL + +2. RQ -> RS: PAYLOAD* + +3. RQ -> RS: COMPLETE + + 同时存在 + +4. RS -> RQ: PAYLOAD* + +5. RS -> RQ: ERROR[APPLICATION_ERROR|REJECTED|CANCELED|INVALID] + +#### 请求方取消,回应方终止 + +1. RQ -> RS: REQUEST_CHANNEL + +2. RQ -> RS: PAYLOAD* + +3. RQ -> RS: COMPLETE + +4. RQ -> RS: CANCEL + + 同时存在 + +5. RS -> RQ: PAYLOAD* + +请求方可能在任意时间发送 PAYLOAD frame。 + +请求方和回应方可能在任意时间发送 REQUEST_N frame。 + +协议的实现**必须**确保请求方只发送一个单一的初始 REQUEST_CHANNEL frame 给回应方。回应方**必须**使用 REQUEST_N frame 作为对初始 REQUEST_CHANNEL frame 的回应。 + +自接收到 CANCEL 起,stream 在接受方终止。 + +自发送完 CANCEL 起,stream 在请求方终止。 + +自接收到 ERROR[APPLICATION_ERROR|REJECTED|CANCELED|INVALID] 起,stream 在请求方和接收方终止。 + +自发送完 ERROR[APPLICATION_ERROR|REJECTED|CANCELED|INVALID] 起,stream 在请求方和接收方终止。 + +没有 ERROR 或者 CANCEL 的情况下,stream 在请求方和回应方都发送并接收到 COMPLETE 后终止。 + +请求方可以通过在初始的 REQUEST_CHANNEL frame 中或者最后一个 PAYLOAD frame 中设置 C 标志位来表示 COMPLETE。请求方**严禁**在发送了带 C 标志位的 frame 之后再发送额外的 PAYLOAD frame。 + +### 流量控制 + +协议提供了多种流量控制机制。 + + + +#### Reactive Streams 语义 + +[Reactive Streams](http://www.reactive-streams.org/) 语义用在 Streams,Subscriptions,以及 Channels 的流控上。这是一个基于信用的模型,请求方授信回应方可以发送的 PAYLOAD 数目。有时这个模型也被称之为 "request-n" 或者 "request(n)"。 + +信用值是累加的。一旦请求方授予了回应方某个信用值,就不能再撤回。比如,发送 `request(3)` 和 `request(2)` 会得到累加值 5,容许回应方发送 5 个 PAYLOAD。 + +需要注意的是,这个行为明确的**不遵循** https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.0/README.md#3-subscription-code 中的规则 17。 + +虽然 Reactive Streams 中信用值可以高达 2^63-1,并且 2^63-1 是一个 magic number,代表可以请求无限数目 ( a magic number signaling to not track demand),但是 RSocket 不支持这个行为。RSocket 优先考虑字节长度,仅使用 4 字节而不是 8 字节,所以 magic number 无效。 + +请求方和回应方**必须**遵从 Reactive Streams 语义。 + +例如,这里展示了一次带流控的流式调用。 + +1. RQ -> RS: REQUEST_STREAM (REQUEST_N=3) +2. RS -> RQ: PAYLOAD +3. RS -> RQ: PAYLOAD +4. RS -> RQ: PAYLOAD +5. 此时 RS 需要等待一个新的 REQUEST_N +6. RQ -> RS: REQUEST_N (N=3) +7. RS -> RQ: PAYLOAD +8. RS -> RQ: PAYLOAD with COMPLETE + + + +#### 租约语义 + +LEASE 语义控制在给定时间段内请求方可以发送的请求(所有类型)数量。 + +对于 LEASE,协议实现的唯一责任是,在请求方遵从它。回应方的应用负责生成的逻辑并通知回应方应当发送一个 LEASE 给对端的请求方。 + +请求方**必须**遵从 LEASE 契约。请求方**严禁**在 __Time-To-Live__ (LEASE frame 中的字段) 指定的时间段之内发送超过在 __Number of Requests __((LEASE frame 中的字段))中指定的请求数。 + +回应方在收到一个由于 LEASE 限制无法处理的请求时**必须**回应一个 ERROR[REJECTED]。这个可以包含在初始 LEASE 里作为[连接建立](#connection-establishment)的一部分发送。 + + + +#### 服务质量和优先级 + +Stream 的服务质量和优先级是应用层和网络层应该考虑的,并且只有它们才能做的更好。元信息的能力,包括 METADATA_PUSH,是应用可以借助调优优先级的有效工具。 + +DiffServ via IP QoS 最好由底层的网络层协议来处理。 + +### 意外的处理 + +本协议对错误 frame 的处理持容忍态度。如果与当前上下文无关的错误**就应该**采取忽略的态度。下面是针对一些场景的进一步澄清: + +1. TCP 半开连接(还有 WebSockets)或者其他类型的死连接可以通过 [Keepalive Frame](#frame-keepalive) 中描述的 KEEPALIVE frame 的缺失来检测。是否因为连接上无活动而关闭连接由应用来决定。 +2. 请求 keepalive 和 timeout 的语义是应用的责任。 +3. 由于 REQUEST_N frame 的缺失而阻止 stream 是应用应该考虑的事情,而**不应当**由协议来处理。 +4. 由于 LEASE frame 的缺失而阻止新的请求是应用应该考虑的事情,而**不应当**由协议来处理。 +5. 如果一个 REQUEST_RESPONSE 的 PAYLOAD 没有设置 COMPLETE 标志位,协议实现**必须**按照标志位被设置来处理。 +6. PAYLOAD 和 REQUEST_CHANNEL 的重组**必须**考虑无限流的可能。 +7. 一个带有 __F__ 和 __C__ 标志位的 PAYLOAD,应当忽略掉其中的 __F__ 标志。 +8. 特定 frame 类型中没有要求的标记位**必须**忽略。 +9. 对于上面部分没有说明的其他情形的 frame,**必须**忽略。比如: + 1. 收到一个已经在使用中的 Stream ID 的 Request frame **必须**忽略。 + 2. 收到未知 Stream ID (包括 0) 上的 CANCEL **必须**忽略。 + 3. 收到未知 Stream ID 上的 ERROR **必须**忽略。 + 4. 收到未知 Stream ID (包括 0) 上的 PAYLOAD **必须**忽略。 + 5. 收到非 0 Stream ID 上的 METADATA_PUSH **必须**忽略。 + 6. 服务器**必须**在接受一个 SETUP 之后忽略后续的 SETUP。 + 7. 服务器**必须**忽略 ERROR[INVALID_SETUP|UNSUPPORTED_SETUP|REJECTED_SETUP|REJECTED_RESUME] frame + 8. 客户端**必须**在连接成功建立之后忽略 ERROR[INVALID_SETUP|UNSUPPORTED_SETUP|REJECTED_SETUP|REJECTED_RESUME] frame + 9. 客户端**必须**忽略 SETUP frame。 + + +  + +----------------- + +[« 动机](README.md) +