Skip to content

NATS 客户端协议详解

NATS 是一个轻量级的高性能消息系统,其客户端协议设计极其简洁:基于 TCP 的纯文本协议,每条指令以 \r\n(CRLF)结尾,字段之间用空白符(空格或制表符)分隔,多个连续空白符视为一个分隔符。操作命令大小写不敏感,但 Subject 名称大小写敏感。

本文完整描述 NATS 客户端协议的握手流程、全部命令格式、Subject 命名规则与通配符、错误码以及保活机制。

连接握手

客户端连接到服务端后,握手按以下顺序进行:

Server → Client:  INFO {...}\r\n
Client → Server:  CONNECT {...}\r\n
Client → Server:  PING\r\n
Server → Client:  PONG\r\n

收到 INFO 后客户端回应 CONNECT 完成鉴权和能力声明。之后客户端主动发送 PING,收到服务端的 PONG 后才算连接真正就绪,可以开始正常的 Pub/Sub 操作。这个 PING/PONG 交换确保了连接双方的通信链路完全通畅。

以下是 NATS 客户端协议完整的交互时序图,涵盖连接握手、订阅、发布投递、Header 消息、取消订阅、保活心跳和错误处理的全流程:

NATS 客户端协议交互时序图

支持的命令

命令方向说明
INFO服务端 → 客户端连接建立后服务端推送的能力声明和配置信息
CONNECT客户端 → 服务端客户端发送的鉴权信息和能力声明,完成握手
PUB客户端 → 服务端发布一条消息到指定 Subject
HPUB客户端 → 服务端发布一条带自定义 Header 的消息到指定 Subject
SUB客户端 → 服务端订阅一个 Subject,支持通配符和队列组
UNSUB客户端 → 服务端取消订阅,支持延迟取消(接收 N 条后自动取消)
MSG服务端 → 客户端服务端向订阅者投递消息
HMSG服务端 → 客户端服务端向订阅者投递带 Header 的消息
PING双向心跳探测,服务端定期发送以检测连接存活;客户端也可主动发送
PONG双向PING 的回应
+OK服务端 → 客户端协议消息确认,仅在 verbose: true 时发送
-ERR服务端 → 客户端错误通知,部分错误会断开连接

各命令详解

INFO

服务端在接受连接后立即主动发送 INFO,将自身能力和连接要求告知客户端。

格式:

INFO {<json>}\r\n

示例:

INFO {"server_id":"Zk0GQ3JBSrg3oyxCRRlE09","server_name":"my-nats","version":"2.10.0","proto":1,"host":"0.0.0.0","port":4222,"headers":true,"auth_required":false,"tls_required":false,"tls_available":false,"max_payload":1048576,"jetstream":false}\r\n

JSON 字段说明:

字段类型是否必须说明
server_idstring服务节点唯一标识符
server_namestring服务节点名称
versionstringNATS Server 版本号
gostring编译所用的 Go 版本
hoststring服务监听的 IP 地址
portint服务监听端口
headersbool是否支持消息 Header(HPUB/HMSG)
max_payloadint允许的最大 Payload 字节数
protoint协议版本号,1 表示支持动态 INFO 更新和 Echo 功能
auth_requiredbool是否要求客户端鉴权
tls_requiredbool是否要求 TLS 加密连接
tls_verifybool是否要求客户端提供证书
tls_availablebool服务端是否可选支持 TLS
client_iduint64服务端分配的内部客户端 ID
client_ipstring客户端的 IP 地址
noncestring用于 NKey 鉴权的随机数
clusterstring集群名称
domainstringNATS 域名
connect_urls[]string集群中其他节点的地址列表,用于客户端重连
ws_connect_urls[]stringWebSocket 连接地址列表
ldmbool服务端是否处于 Lame Duck 模式(即将下线)
jetstreambool是否支持 JetStream
git_commitstring编译版本的 Git commit hash
cluster_dynamicbool集群是否支持动态路由(dynamic routes),仅在配置了集群路由时存在
xkeystring服务端的 X25519 公钥,用于消息级加密(NKey 加密扩展),普通部署不使用

proto >= 1 时,服务端可能在连接建立后随时推送新的 INFO 消息,客户端应持续监听并更新本地的集群拓扑信息。

CONNECT

客户端收到 INFO 后,必须回应 CONNECT 以完成握手,提供鉴权信息和能力声明。

格式:

CONNECT {<json>}\r\n

示例:

CONNECT {"verbose":false,"pedantic":false,"tls_required":false,"name":"my-client","lang":"go","version":"1.34.0","protocol":1,"echo":true,"headers":true}\r\n

JSON 字段说明:

字段类型是否必须说明
verbosebool是否启用 +OK 确认回包,设为 false 可减少无意义流量
pedanticbool是否开启严格格式校验
tls_requiredbool是否要求 TLS 连接
langstring客户端实现语言,如 gorustjava
versionstring客户端库版本号
namestring客户端名称,用于服务端日志
protocolint协议版本,1 表示支持动态 INFO 更新
auth_tokenstring条件必须Token 鉴权时使用
userstring条件必须用户名/密码鉴权时使用
passstring条件必须密码,与 user 配合使用
echobool是否将自己发布的消息回传给自己的订阅,默认 true
sigstring条件必须nonce 的签名,NKey 鉴权时使用
jwtstring用户 JWT,用于权限控制
nkeystringNKey 公钥
no_respondersbool是否启用无订阅者时立即返回错误的功能
headersbool是否支持消息 Header

PUB

客户端向服务端发布消息。

格式:

PUB <subject> [reply-to] <#bytes>\r\n[payload]\r\n

字段:

字段说明
subject消息目标 Subject(必填)
reply-to可选的回复地址,订阅者可向此 Subject 回应
#bytesPayload 的字节数(必填,无内容时填 0
payload消息内容,紧跟控制行之后,结尾有 \r\n

示例:

# 发布一条消息
PUB FOO 11\r\n
Hello NATS!\r\n

# 带 reply-to 的发布
PUB FRONT.DOOR JOKE.22 11\r\n
Knock Knock\r\n

# 空 Payload
PUB NOTIFY 0\r\n
\r\n

HPUB

带自定义 Header 的消息发布,Header 格式与 HTTP/1 相同。

格式:

HPUB <subject> [reply-to] <#header bytes> <#total bytes>\r\n[headers]\r\n\r\n[payload]\r\n

字段:

字段说明
subject消息目标 Subject(必填)
reply-to可选的回复地址,订阅者可向此 Subject 回应
#header bytesHeader 部分的字节数,包含状态行、键值对及末尾空行(\r\n
#total bytesHeader + Payload 的总字节数;#total bytes - #header bytes 即为 Payload 字节数,为 0 时表示无 Payload
headersHeader 内容,以 NATS/1.0\r\n 开头,后跟若干 Name: Value\r\n 键值对,以空行结束
payload消息内容,位于 Header 之后,可为空

示例:

HPUB FOO 22 33\r\n
NATS/1.0\r\n
Header: value\r\n
\r\n
Hello NATS!\r\n

其中 #header bytes = 22(Header 部分含末尾空行的长度),#total bytes = 33(Header + Payload 的总长度)。

SUB

客户端订阅一个 Subject。

格式:

SUB <subject> [queue group] <sid>\r\n

字段:

字段说明
subject订阅的 Subject,支持通配符 *>
queue group可选的队列组名称,同一队列组内多个订阅者只有一个收到消息,实现负载均衡
sid本连接内唯一的订阅 ID,协议层面是字符串类型(不限于数字),用于后续 UNSUB 和消息推送时标识

示例:

# 普通订阅
SUB FOO 1\r\n

# 队列组订阅
SUB BAR workers 44\r\n

# sid 可以是任意字符串
SUB BAZ my-sub-id\r\n

UNSUB

取消订阅,或设置自动在收到 N 条消息后取消。

格式:

UNSUB <sid> [max_msgs]\r\n

字段:

字段说明
sid要取消的订阅 ID,与 SUB 时指定的 sid 对应
max_msgs可选,设置后不立即取消,而是再接收 max_msgs 条消息后自动取消;省略则立即取消

示例:

# 立即取消
UNSUB 1\r\n

# 再接收 5 条后自动取消
UNSUB 1 5\r\n

MSG

服务端向订阅者推送消息。

格式:

MSG <subject> <sid> [reply-to] <#bytes>\r\n[payload]\r\n

字段:

字段说明
subject消息实际发布的 Subject
sid服务端匹配到的订阅 ID,与 SUB 时客户端指定的 sid 对应
reply-to可选,发布者填写的回复地址,订阅者可向此 Subject 发送响应
#bytesPayload 的字节数,无内容时为 0
payload消息内容,紧跟控制行之后,结尾有 \r\n

示例:

# 无 reply-to
MSG FOO.BAR 9 11\r\n
Hello World\r\n

# 带 reply-to
MSG FOO.BAR 9 GREETING.34 11\r\n
Hello World\r\n

HMSG

服务端推送带 Header 的消息。

格式:

HMSG <subject> <sid> [reply-to] <#header bytes> <#total bytes>\r\n[headers]\r\n\r\n[payload]\r\n

字段:

字段说明
subject消息实际发布的 Subject
sid服务端匹配到的订阅 ID,与 SUB 时客户端指定的 sid 对应
reply-to可选,发布者填写的回复地址
#header bytesHeader 部分的字节数,包含状态行、键值对及末尾空行(\r\n
#total bytesHeader + Payload 的总字节数;#total bytes - #header bytes 即为 Payload 字节数,为 0 时表示无 Payload
headersHeader 内容,以 NATS/1.0\r\n(或 NATS/1.0 <status>\r\n)开头,后跟若干 Name: Value\r\n,以空行结束
payload消息内容,位于 Header 之后,可为空

Header 格式与 HPUB 相同,以 NATS/1.0\r\n 开头。

Header 的状态行除了 NATS/1.0 之外,还可以携带状态码和描述信息。例如当客户端在 CONNECT 中设置了 no_responders: true,对一个没有任何订阅者的 Subject 执行请求-响应时,服务端会返回:

HMSG FOO 1 16 16\r\n
NATS/1.0 503\r\n
\r\n
\r\n

其中 503 表示 No Responders,客户端可以据此立即得知没有可用的服务端,而不是一直等待超时。

PING 和 PONG

用于连接保活和连接就绪确认。

格式:

PING\r\n
PONG\r\n

PING/PONG 有两个用途:

  1. 连接就绪确认:握手阶段,客户端发送 CONNECT 后主动发送 PING,收到 PONG 后确认连接就绪。
  2. 连接保活:服务端按固定间隔向客户端发送 PING,客户端必须在超时时间内回应 PONG,否则服务端会以 Stale Connection 错误断开连接。客户端也可以主动发送 PING,服务端会回应 PONG。正常的数据流量(如 PUB)也会重置保活计时器。

+OK 和 -ERR

服务端的协议响应。

格式:

+OK\r\n
-ERR '<error message>'\r\n

+OK 仅在 CONNECTverbose: true 时才会发送,用于确认每条收到的协议消息。默认客户端都设置 verbose: false 以避免无意义流量。

-ERR 分两类:断开连接的错误和保留连接的错误。

断开连接的错误:

错误信息说明
Unknown Protocol Operation收到无法识别的命令
Attempted To Connect To Route Port客户端误连到了集群内部端口
Authorization Violation鉴权失败
Authorization Timeout客户端连接后超时未完成鉴权(默认 1 秒)
Invalid Client ProtocolCONNECT 中的协议版本号无效
Maximum Control Line ExceededSubject 或控制行超过最大长度(默认 1024 字节)
Parser Error协议消息无法解析
Secure Connection - TLS Required服务端要求 TLS 但客户端未使用
Stale Connection客户端长时间未响应 PING
Maximum Connections Exceeded服务端连接数达到上限(默认 64K)
Slow Consumer客户端消费过慢,服务端待发缓冲区达到上限(默认 10MB)
Maximum Payload ViolationPayload 超过服务端的 max_payload 限制

保留连接的错误(不断开):

错误信息说明
Invalid SubjectSubject 格式非法
Permissions Violation for Subscription to <subject>无该 Subject 的订阅权限
Permissions Violation for Publish to <subject>无该 Subject 的发布权限

Subject 命名规则

Subject 由字母、数字和 UTF-8 字符组成,以 . 作为层级分隔符。

合法示例:

FOO
foo.bar
FOO.BAR.BAZ
foo.bar.1

非法示例:

FOO. BAR      # 不能包含空格
foo..bar       # 不能有空 token

通配符

NATS 支持两种通配符,仅用于 SUB,不能用于 PUB

单层通配符 *

匹配一个 token。可以出现在任意层级位置。

foo.*.baz  匹配 foo.bar.baz,不匹配 foo.bar.qux.baz
foo.*      匹配 foo.bar,不匹配 foo.bar.baz

多层通配符 >

匹配一个或多个 token,必须出现在 Subject 的最后一个位置。

foo.>   匹配 foo.bar、foo.bar.baz、foo.bar.baz.qux
>       匹配所有 Subject

通配符必须是独立的 token,foo*f*o 是非法的。

分帧与解析

协议解析分两步:

  1. 读控制行:扫描字节流找到 \r\n,按命令词解析字段
  2. 读 Payload:控制行中包含 #bytes 字段,读取对应字节数,再消耗末尾的 \r\n

对于 PING、PONG、+OK、-ERR、SUB、UNSUB,控制行就是完整的消息,不涉及 Payload。

对于 PUB 和 MSG,需要在控制行解析完成后,再额外读取 #bytes + 2 个字节(Payload 内容 + 尾部 \r\n)。

对于 HPUB 和 HMSG,控制行中包含 #header bytes#total bytes 两个长度字段。解析时先读取 #total bytes + 2 个字节,其中前 #header bytes 个字节为 Header 部分(含 NATS/1.0\r\n 状态行、键值对和末尾空行),剩余字节为 Payload。

#bytes = 0(PUB/MSG)或 #total bytes = #header bytes(HPUB/HMSG,即无 Payload)时,Payload 为空,仍然需要消耗末尾的 \r\n(即 2 个字节)。

参考资料

🎉 既然都登录了 GitHub,不如顺手给我们点个 Star 吧!⭐ 你的支持是我们最大的动力 🚀