因为项目上在和其他团队联调时需要抓包分析消息正确性的问题,因此在网络上查找了一下关于ZeroMQ的协议资料。找到如下文章(原文)。另外这里有一篇对ZeroMQ实现讲的比较深的文章.

ZeroMQ消息传输协议(ZMTP)是一种传输层协议,用于通过连接的传输层(如TCP)在两个对等体之间交换消息。本文档描述了ZMTP/2.0。

目标

ZeroMQ消息传输协议(ZMTP)是一种传输层协议,用于通过连接的传输层(如TCP)在两个对等体之间交换消息。本文档描述了ZMTP/2.0。

理论上,ZMTP应该定义实现之间完全可互操作的行为。但是,部分必要的语义只在libzmq的代码和参考手册中定义。我们希望随着时间的推移,这些语义将被正确地提取、抽象、文档化,并通过独立的代码进行验证。

实现

正式的语法

以下ABNF语法定义了ZMTP/2.0协议:

zmtp        = *connection

connection  = greeting *message

greeting    = signature revision socket-type identity
signature   = %xFF 8%x00 %x7F
revision    = %x01

socket-type = PAIR | PUB | SUB | REQ | REP | DEALER | ROUTER | PULL | PUSH
PAIR        = %X00
PUB         = %X01
SUB         = %X02
REQ         = %X03
REP         = %X04
DEALER      = %X05
ROUTER      = %X06
PULL        = %X07
PUSH        = %X08

identity    = final-short body
final-short = %x00 OCTET
body        = *OCTET

message     = *more-frame final-frame
final-frame = final body
final       = final-short | final-long
final-long  = %x02 8OCTET
more-frame  = more body
more        = more-short | more-long
more-short  = %x01 OCTET
more-long   = %x03 8OCTET

分帧 (Framing)

ZMTP将TCP流分隔为“帧(frame)”。为了便于构造,消息可以由多个帧组成。一个帧由一个标志(flag)字段、一个长度(length)字段和一个等于长度字段值的长度的帧体组成。长度不包括标志字段,也不包括本身,因此空帧的长度为0。

标志字段由包含各种控制标志的单个byte(8-bit)组成。位0是最低有效位(最右边的位):

  • 第0位 (MORE): 后面还有帧。值为0表示没有更多的帧(即这就是最后帧)。值为1表示后面会有更多帧。对于由单帧组成的消息,MORE位必须为0。
  • 第1位 (LONG): 长消息。值为0表示将消息长度只占单个字节。值为1表示按网络字节顺序将消息长度编码为64位无符号整数。
  • 第2-7位: 保留。第2-7位保留将来使用,且必须为零。

下图显示了长度为0到255字节(短帧,长度只占一个字节)的最终帧的布局:

+-----------------+
 Octet 0    | 0 0 0 0 0 0 0 0 |
            +-----------------+
 Octet 1    | Length          |
            +-----------------+- ... -----------------+
 Octets 2+  | Body                      Length octets |
            +------------------- ... -----------------+

下图显示了长格式(LONG,body超过255个字节)最终帧的布局:

+-----------------+
 Octet 0    | 0 0 0 0 0 0 1 0 |
            +-----------------+
 Octets 1-8 | Length                       8 octets   |
            +------------------ ... ------------------+
 Octets 9+  | Body                      Length octets |
            +------------------ ... ------------------+

套接字的兼容性

实现应该强制传入连接具有有效的套接字类型,这取决于接收连接的套接字的套接字类型:

  • PAIR: 接受来自PAIR的连接。
  • PUB: 接受来自SUB的连接。
  • SUB: 接受来自PUB的连接。
  • REQ: 接受来自REP或ROUTER的连接。
  • REP: 接受来自REQ或DEALER的连接。
  • DEALER: 接受来自REP、DEALER或ROUTER的连接。
  • ROUTER: 接受来自REQ、DEALER或ROUTER的连接。
  • PULL: 接受来自PUSH的连接。
  • PUSH: 接受来自PULL的连接。

任何其他套接字组合都应该通过静默地断开其他对等体的连接来处理,并可能记录错误以供调试之用。

发布-订阅

XPUB和XSUB套接字在协议级别上作为PUB和SUB套接字实现。也就是说,XPUB和XSUB只是API结构。SUB套接字发送订阅消息为一个字节%x01,后面跟着订阅主体;取消订阅消息为一个字节%x00,后面跟着订阅主体。

向后的互操作性

ZMTP/2.0使用单个字节来表示协议修订号(revision)。ZMTP/2.0被认为是该协议的第1版。ZMTP/1.0(参见“13/ZMTP - ZeroMQ消息传输协议”)没有任何版本信息。但是,实现可以检测ZMTP/1.0实现并与之互操作。

如果实现不希望向后兼容ZMTP/1.0对等点,那么它应该使用上面语法中定义的签名。为了检测并与ZMTP/1.0对等体互操作,一个实现应该在打开TCP套接字后立即:

  • 发送一个10字节的签名,由“%xFF长度%x7F”组成,其中“长度”是发送者身份(ID)的长度(0或更多字节)加上1。长度必须是8字节的网络字节顺序。
  • 读来自对端的第一个字节。
  • 如果这个字节不是%FF,那么这个对端正在使用ZMTP/1.0。
  • 如果这个字节是%FF,那么我们再读取9个字节,并检查最后一个字节(第10个)。如果最低位是0,那么对端使用ZMTP/1.0。
  • 如果最低位不为0,则对端使用ZMTP/2.0或更高版本。我们再读取两个字节,它们表示协议修订(revision)和对端的套接字类型。然后,我们可以使用ZMTP/2.0帧语法对该连接上的所有后续帧进行编码/解码。
  • 当我们检测到对端是ZMTP/1.0时,我们已经发送了10个字节,对端将其解释为标识帧的开始。我们继续发送标识帧的主体(0个或多个字节)。从那时起,我们使用ZMTP/1.0帧语法对该连接上的所有帧进行编码和解码。

安全

ZMTP/2.0在安全性方面没有做任何尝试,应用程序可以在安全性之上进行分层。