11/MTL
消息传输层 (Message Transport Layer)
- 状态:草案 (Status: draft)
- 编辑:Pieter Hintjens ph@imatix.com
消息传输层 (Message Transport Layer)
本文档提出了一个消息传输层(MTL),它是一个面向连接的协议,支持基于代理的消息传递。MTL 将一组客户端与中心消息代理连接起来,允许客户端向代理发出命令、向代理发送消息,并从代理接收消息。
许可证 (License)
版权所有 (c) 2011 作者。
本规范是自由软件;您可以根据自由软件基金会发布的 GNU 通用公共许可证的条款对其进行再分发和/或修改;无论是该许可证的第 3 版,还是(由您选择)任何后续版本。
本规范的发布是希望它有用,但不提供任何担保;甚至不包括对适销性或适用于特定目的的适用性的默示担保。详情请参见 GNU 通用公共许可证。
您应该已经随本程序收到了 GNU 通用公共许可证的副本;如果未收到,请参见 https://gnu.ac.cn/licenses。
变更流程 (Change Process)
本规范是自由开放标准(参见“自由开放标准的定义”),并受数字标准组织共识导向规范系统 (COSS) 管辖(参见“共识导向规范系统”)。
语言 (Language)
本文档中的关键词“MUST”(必须)、“MUST NOT”(不得)、“REQUIRED”(要求)、“SHALL”(应)、“SHALL NOT”(不应)、“SHOULD”(应该)、“SHOULD NOT”(不应)、“RECOMMENDED”(建议)、“MAY”(可以)和“OPTIONAL”(可选)应按照 RFC 2119 中的描述解释(参见“RFC 中表示要求级别的关键词”)。
请注意,在本文档中我们不正式使用术语“消息”,因为该术语在多个层面被重载。本规范的名称需要审查。
目标 (Goals)
MTL 是一个面向连接的协议,支持基于代理的消息传递。它将一组客户端与代理连接起来,允许客户端向代理发出命令、向代理发送内容,并从代理接收内容。
MTL 的主要目标是
- 支持基于可扩展 profiles 的任意消息传递语义。
- 提供客户端到服务器的简单同步命令流程。
- 提供客户端到服务器或从服务器的快速异步数据流程。
- 使用 SASL(参见“RFC 2222 - 简单认证和安全层 (SASL)”)提供客户端认证。
- 提供安全的前向和后向兼容性。
- 提供对死机或阻塞的对端等错误的检测。
- 提供健壮的错误处理。
总体运行 (General Operation)
MTL 将所有客户端-服务器活动分为两个独立的流
-
同步的低容量 控制流。控制流严格遵循请求-响应模式,即客户端的一个请求总是对应服务器的一个响应。服务器从不未经理请求发送响应。客户端使用其请求套接字的超时来检测死机或不存在的服务器。
-
异步的高容量 数据流。数据流是双向和异步的,客户端或服务器可以随时发送各种类型的帧。客户端或服务器可以使用心跳来检测死机对端。
控制流承载 JSON(参见“[]()”)命令。目的是使控制内容易于解析、扩展和调试。
数据流承载二进制编码的命令和内容。目的是使数据内容解码和编码速度快,并在线路上紧凑。
控制流 (Control Flow)
控制命令通过客户端到服务器的请求-回复流程进行。服务器打开一个 0MQ ROUTER 套接字,并绑定到其已知端点。客户端打开一个 REQ 套接字并连接到此端点。然后客户端发出请求,服务器发出响应。连接永久持续,或者直到客户端关闭其套接字,或者直到服务器解除绑定其端点。
客户端到服务器的每个请求都遵循此格式
- 帧 0:命令名称 (字符串)
- 帧 1:请求体 (JSON字符串)
服务器到客户端的每个回复都遵循此格式
- 帧 0:状态码 (三位数, 2xx-5xx)
- 帧 1:回复体 (JSON字符串)
命令分为 类,每个类提供一组 方法。每个方法包括客户端请求和服务器回复,序列化为 JSON。服务器回复按状态码分类。
命令名称不区分大小写。
客户端认证 (Client Authentication)
在新连接上,客户端 MUST(必须)首先发送一个 Connection.Open 命令。这请求访问服务器上的特定 虚拟主机
Connection.Open
------------------------------------------------------------
{
"protocol": {
"name": "MTL",
"version": 1
},
"virtual-host": "test-env"
}
客户端使用 SASL 挑战-响应模型进行认证。服务器 MAY(可以)接受与未经认证的客户端一起工作。然后它将回复 201 Ready
201
------------------------------------------------------------
{
"status": "Ready",
"profiles": [ "profile name", "profile name",... ]
}
为了挑战客户端,服务器响应 401 Unauthorized 并指定接受的 SASL 机制列表
401
------------------------------------------------------------
{
"status": "Unauthorized",
"mechanisms": [ "mechanism name", "mechanism name",... ]
}
服务器 MAY(可以)随时发出 401 响应,例如,如果客户端的认证信息已更改或过期。客户端 MUST(必须)能够随时重新认证。
为了认证,客户端发送 Connection.Authorize
Connection.Authorize
------------------------------------------------------------
{
"mechanism" : "mechanism name",
"response" : "BASE64-encoded blob"
}
如果 SASL 挑战/响应周期完成,服务器回复 201 Ready。否则服务器响应 402 Challenged,之后客户端 MUST(必须)使用新的响应数据重新发送新的 Connection.Authorize
402
------------------------------------------------------------
{
"status": "Challenged",
"mechanism": "mechanism name",
"challenge" : "BASE64-encoded blob"
}
SASL 挑战-响应周期可以重复多次。
选择 Profile (Selecting a Profile)
为了开始使用特定的 profile,客户端发出 Connection.Profile 命令
Connection.Profile
------------------------------------------------------------
{
"profile" : "profile name"
}
profile 名称必须是服务器在发出 201 Ready 时指定的 profile 之一。
如果 profile 有效,服务器响应 200 OK。如果无效,服务器响应 400 Bad Request。
200
------------------------------------------------------------
{
"status": "OK"
}
profile 名称不区分大小写。
每个 profile 定义一组类,客户端可以使用这些类与服务器端资源交互。本文档后面定义了一个“test” profile。
请求数据租约 (Requesting a Data Lease)
数据流可以从一组资源读取,也可以向一组资源写入。为了打开数据流,客户端使用 Connection.Reader 或 Connection.Writer 命令请求一个“租约”
Connection.Reader
------------------------------------------------------------
{
"resources": [ "resource name", "resource name", ...],
"confirm": "none | full"
}
Connection.Writer
------------------------------------------------------------
{
"resources": [ "resource name", "resource name", ...],
"confirm": "none | full"
}
客户端 MUST(必须)在发出 Connection.Reader 或 Connection.Writer 请求之前,已经选择了一个 profile。
资源列表指定请求适用于哪些资源。一个空列表表示“服务器上的任何及所有资源”。
“confirm”字段指定消息是否被确认。如果服务器接受 Connection.Reader 或 Connection.Writer 请求,它将响应 202 Data Lease
202
------------------------------------------------------------
{
"status": "Data Lease",
"port": port number
"lease" : "data lease"
}
如果 profile 不支持请求的确认模型,服务器 MUST(必须)响应 501 Not Implemented
501
------------------------------------------------------------
{
"status": "Not implemented"
}
控制流错误处理 (Control Flow Error Handling)
服务器 SHOULD(应该)静默丢弃格式错误的请求,即帧数错误或内容不是有效的 JSON 的请求。服务器 MUST(必须)对格式正确但因任何原因无效的请求作出合适的错误回复。
数据流 (Data Flow)
数据命令通过客户端到服务器的异步双向流进行。服务器打开一个 0MQ ROUTER 套接字并将其绑定到一个端口,并在 202 回复中将此端口告知客户端。客户端打开一个 DEALER 套接字并使用此端口连接到服务器端点。然后客户端和服务器可以发出数据命令。连接永久持续,或者直到客户端关闭其套接字,或者直到服务器解除绑定其端点。
服务器 MAY(可以)在与控制流相同的套接字上处理数据流,或者 MAY(可以)为数据流打开单独的套接字。
数据命令编码 (Data Command Encoding)
每个数据命令遵循此格式
- 帧 0:头部 (二进制编码)
- 帧 1:主体 (不透明二进制内容)
头部帧编码如下
- 1字节命令类型
- 可选字段
取决于命令类型,命令类型后面跟着零个或多个可选字段,其编码和含义按命令类型指定。
初始化数据流 (Initializing a Data Flow)
当客户端打开数据连接后,必须发送 Lease 命令,如下所示
- 类型 = 0x01, Lease
- 字段 1:命令序列 (整数)
- 字段 2:服务器 202 响应提供的数据租约 (短字符串)
- 主体:未使用
服务器 MUST(必须)回复 Response 命令,该命令回显客户端使用的命令序列,如下所示
- 类型 = 0x02, Response
- 字段 1:命令序列 (整数)
- 主体:三位响应码
其中响应码可以是
- “200” - 租约成功
- “403” - 租约被禁止
命令序列在此命令和其它命令中的目的是允许客户端对命令进行流水线处理,即无需等待每个命令的响应即可发送多个命令。服务器 MAY(可以)按任意顺序返回 Response 命令。
召唤资源 (Summoning a Resource)
为了开始接收资源中的内容,客户端发送 Summon 命令,如下所示
- 类型 = 0x03, Summon
- 字段 1:命令序列 (整数)
- 字段 2:资源名称 (短字符串)
- 主体:未使用
其中资源名称 MUST(必须)是此数据租约的 Connection.Reader 请求指定的资源之一。在没有介入的 dismiss 之前,客户端 MAY NOT(不得)两次召唤同一个资源。
服务器 MUST(必须)回复 Response 命令,其中响应码可以是
- “200” - Summon 命令成功
- “400” - 资源无法被召唤。
在成功的 Summon 命令之后,服务器端资源 MAY(可以)向客户端发送 Content 命令。
解散资源 (Dismissing a Resource)
为了停止接收资源中的内容,客户端发送 Dismiss 命令,如下所示
- 类型 = 0x04, Dismiss
- 字段 1:命令序列 (整数)
- 字段 2:资源名称 (短字符串)
- 主体:未使用
其中资源名称 MUST(必须)是此数据租约的 Connection.Reader 请求指定的资源之一。在没有介入的 summon 之前,客户端 MAY NOT(不得)两次解散同一个资源。
服务器 MUST(必须)回复 Response 命令,其中响应码可以是
- “200” - Dismiss 命令成功
- “400” - 资源无法被解散。
在成功的 Dismiss 命令之后,服务器端资源 MUST NOT(不得)再向客户端发送 Contents。
将内容发送至资源 (Sending Content to a Resource)
为了将内容发送到资源,客户端发送 Content 命令,如下所示
- 类型 = 0x05, Content
- 字段 1:内容序列 (整数)
- 字段 2:资源名称 (短字符串),可为空
- 字段 3:属性 (字段表)
- 主体:不透明二进制内容
如果 Connection.Writer 请求指定了多个资源,则资源名称是必需的。
从资源接收内容 (Receiving Content from a Resource)
当资源有内容要发送给客户端时,服务器发送 Content 命令,如下所示
- 类型 = 0x05, Content
- 字段 1:内容序列 (整数)
- 字段 2:资源名称 (短字符串),可为空
- 字段 3:属性 (字段表)
- 主体:不透明二进制内容
服务器 MUST(必须)在该资源发送任何 Content 之前发送对 Summon 命令的 Response,并且服务器在对 Dismiss 命令的成功 Response 之后,MUST NOT(不得)再从该资源发送任何 Content。如果 Connection.Reader 请求指定了多个资源,则资源名称是必需的。
内容确认 (Content Confirmation)
数据流在一个方向上的所有 Content 命令要么被确认,要么未被确认,取决于相应的 Connection.Reader 或 Connection.Writer 中指定的“confirm”值
- “none” - Content 命令未被确认。
- “full” - Content 命令被确认。
确认机制如下
- 一个对端发送一个或多个 Content 命令。
- 接收方使用 Confirm 命令确认安全接收了一个或多个 Content 命令。
- 接收方使用 Reject 命令拒绝了一个或多个 Content 命令。
Confirm 命令格式如下
- 类型 = 0x06, Confirm
- 字段 1:内容序列 (整数)
- 字段 1:内容序列 (整数)
- 主体:未使用
字段 2:multiple (布尔值)
如果“multiple”字段为 TRUE,Confirm 命令覆盖指定内容序列及之前的所有 Contents。如果“multiple”字段为 FALSE,Confirm 仅覆盖一个 Content。
对 Confirm 命令没有响应。无效的 Confirm 命令被静默忽略。
- Reject 命令格式如下
- 字段 1:内容序列 (整数)
- 字段 1:内容序列 (整数)
- 类型 = 0x07, Reject
- 主体:未使用
字段 1:内容序列 (整数)
字段 2:multiple (布尔值)
字段 3:recycle (布尔值)
心跳 (Heartbeating)
如果“multiple”字段为 TRUE,Reject 命令覆盖指定内容序列及之前的所有 Contents。如果“multiple”字段为 FALSE,Reject 仅覆盖一个 Content。
- 如果“recycle”字段为 TRUE,Content 将根据 profile 相关的启发式算法重发。
- 主体:未使用
对 Reject 命令没有响应。无效的 Reject 命令被静默忽略。
- 客户端或服务器可以通过发送 Ping 命令对否则静默的数据连接进行心跳
- 主体:未使用
类型 = 0x08, Ping
Ping 命令的接收方应该回复 Pong 命令
字段编码 (Field Encodings)
类型 = 0x09, Pong
- 客户端或服务器 SHOULD(应该)在没有发送其他命令时发送 Ping 命令。Ping 命令的频率应该是可配置的。
- 客户端或服务器 SHOULD(应该)如果在特定时间段内没有收到 Pong,则认为其对端“已断开”。
- 以下是可能的字段编码
- 短字符串:一个单字节,后跟 0 到 255 字节的可打印字符。
- 整数:以网络字节序表示的八字节(64位)无符号值。
数据流错误处理 (Data Flow Error Handling)
短整数:以网络字节序表示的两字节(16位)无符号值。
请求和回复路由 (Request and Reply Routing)
布尔值:一个单字节,可能的值为 0 (FALSE) 或 1 (TRUE)。
字段表:待定义。
服务器 SHOULD(应该)静默丢弃格式错误的请求,即帧数错误或内容无效的请求。服务器 MUST(必须)对格式正确但因任何原因无效的请求作出合适的错误回复。
测试 Profile (The Test Profile)
服务器 MUST(必须)使用 ROUTER 套接字。根据 ØMQ 参考手册(参见“[]()”)
- 接收消息时,ROUTER 套接字应在将消息传递给应用程序之前,在消息前面添加包含发起对端身份的消息部分。发送消息时,ROUTER 套接字应移除消息的第一部分并使用它来确定消息将被路由到的对端的身份。
- 在此讨论中忽略此额外帧。
- “test” profile 定义了以下用于内容的路由和队列语义
服务器只提供一个资源,称为“resource”。