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 消息、取消订阅、保活心跳和错误处理的全流程:

支持的命令
| 命令 | 方向 | 说明 |
|---|---|---|
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\nJSON 字段说明:
| 字段 | 类型 | 是否必须 | 说明 |
|---|---|---|---|
server_id | string | 是 | 服务节点唯一标识符 |
server_name | string | 是 | 服务节点名称 |
version | string | 是 | NATS Server 版本号 |
go | string | 是 | 编译所用的 Go 版本 |
host | string | 是 | 服务监听的 IP 地址 |
port | int | 是 | 服务监听端口 |
headers | bool | 是 | 是否支持消息 Header(HPUB/HMSG) |
max_payload | int | 是 | 允许的最大 Payload 字节数 |
proto | int | 是 | 协议版本号,1 表示支持动态 INFO 更新和 Echo 功能 |
auth_required | bool | 否 | 是否要求客户端鉴权 |
tls_required | bool | 否 | 是否要求 TLS 加密连接 |
tls_verify | bool | 否 | 是否要求客户端提供证书 |
tls_available | bool | 否 | 服务端是否可选支持 TLS |
client_id | uint64 | 否 | 服务端分配的内部客户端 ID |
client_ip | string | 否 | 客户端的 IP 地址 |
nonce | string | 否 | 用于 NKey 鉴权的随机数 |
cluster | string | 否 | 集群名称 |
domain | string | 否 | NATS 域名 |
connect_urls | []string | 否 | 集群中其他节点的地址列表,用于客户端重连 |
ws_connect_urls | []string | 否 | WebSocket 连接地址列表 |
ldm | bool | 否 | 服务端是否处于 Lame Duck 模式(即将下线) |
jetstream | bool | 否 | 是否支持 JetStream |
git_commit | string | 否 | 编译版本的 Git commit hash |
cluster_dynamic | bool | 否 | 集群是否支持动态路由(dynamic routes),仅在配置了集群路由时存在 |
xkey | string | 否 | 服务端的 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\nJSON 字段说明:
| 字段 | 类型 | 是否必须 | 说明 |
|---|---|---|---|
verbose | bool | 是 | 是否启用 +OK 确认回包,设为 false 可减少无意义流量 |
pedantic | bool | 是 | 是否开启严格格式校验 |
tls_required | bool | 是 | 是否要求 TLS 连接 |
lang | string | 是 | 客户端实现语言,如 go、rust、java |
version | string | 是 | 客户端库版本号 |
name | string | 否 | 客户端名称,用于服务端日志 |
protocol | int | 否 | 协议版本,1 表示支持动态 INFO 更新 |
auth_token | string | 条件必须 | Token 鉴权时使用 |
user | string | 条件必须 | 用户名/密码鉴权时使用 |
pass | string | 条件必须 | 密码,与 user 配合使用 |
echo | bool | 否 | 是否将自己发布的消息回传给自己的订阅,默认 true |
sig | string | 条件必须 | 对 nonce 的签名,NKey 鉴权时使用 |
jwt | string | 否 | 用户 JWT,用于权限控制 |
nkey | string | 否 | NKey 公钥 |
no_responders | bool | 否 | 是否启用无订阅者时立即返回错误的功能 |
headers | bool | 否 | 是否支持消息 Header |
PUB
客户端向服务端发布消息。
格式:
PUB <subject> [reply-to] <#bytes>\r\n[payload]\r\n字段:
| 字段 | 说明 |
|---|---|
subject | 消息目标 Subject(必填) |
reply-to | 可选的回复地址,订阅者可向此 Subject 回应 |
#bytes | Payload 的字节数(必填,无内容时填 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\nHPUB
带自定义 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 bytes | Header 部分的字节数,包含状态行、键值对及末尾空行(\r\n) |
#total bytes | Header + Payload 的总字节数;#total bytes - #header bytes 即为 Payload 字节数,为 0 时表示无 Payload |
headers | Header 内容,以 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\nUNSUB
取消订阅,或设置自动在收到 N 条消息后取消。
格式:
UNSUB <sid> [max_msgs]\r\n字段:
| 字段 | 说明 |
|---|---|
sid | 要取消的订阅 ID,与 SUB 时指定的 sid 对应 |
max_msgs | 可选,设置后不立即取消,而是再接收 max_msgs 条消息后自动取消;省略则立即取消 |
示例:
# 立即取消
UNSUB 1\r\n
# 再接收 5 条后自动取消
UNSUB 1 5\r\nMSG
服务端向订阅者推送消息。
格式:
MSG <subject> <sid> [reply-to] <#bytes>\r\n[payload]\r\n字段:
| 字段 | 说明 |
|---|---|
subject | 消息实际发布的 Subject |
sid | 服务端匹配到的订阅 ID,与 SUB 时客户端指定的 sid 对应 |
reply-to | 可选,发布者填写的回复地址,订阅者可向此 Subject 发送响应 |
#bytes | Payload 的字节数,无内容时为 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\nHMSG
服务端推送带 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 bytes | Header 部分的字节数,包含状态行、键值对及末尾空行(\r\n) |
#total bytes | Header + Payload 的总字节数;#total bytes - #header bytes 即为 Payload 字节数,为 0 时表示无 Payload |
headers | Header 内容,以 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\nPING/PONG 有两个用途:
- 连接就绪确认:握手阶段,客户端发送
CONNECT后主动发送PING,收到PONG后确认连接就绪。 - 连接保活:服务端按固定间隔向客户端发送
PING,客户端必须在超时时间内回应PONG,否则服务端会以Stale Connection错误断开连接。客户端也可以主动发送PING,服务端会回应PONG。正常的数据流量(如 PUB)也会重置保活计时器。
+OK 和 -ERR
服务端的协议响应。
格式:
+OK\r\n
-ERR '<error message>'\r\n+OK 仅在 CONNECT 中 verbose: true 时才会发送,用于确认每条收到的协议消息。默认客户端都设置 verbose: false 以避免无意义流量。
-ERR 分两类:断开连接的错误和保留连接的错误。
断开连接的错误:
| 错误信息 | 说明 |
|---|---|
Unknown Protocol Operation | 收到无法识别的命令 |
Attempted To Connect To Route Port | 客户端误连到了集群内部端口 |
Authorization Violation | 鉴权失败 |
Authorization Timeout | 客户端连接后超时未完成鉴权(默认 1 秒) |
Invalid Client Protocol | CONNECT 中的协议版本号无效 |
Maximum Control Line Exceeded | Subject 或控制行超过最大长度(默认 1024 字节) |
Parser Error | 协议消息无法解析 |
Secure Connection - TLS Required | 服务端要求 TLS 但客户端未使用 |
Stale Connection | 客户端长时间未响应 PING |
Maximum Connections Exceeded | 服务端连接数达到上限(默认 64K) |
Slow Consumer | 客户端消费过慢,服务端待发缓冲区达到上限(默认 10MB) |
Maximum Payload Violation | Payload 超过服务端的 max_payload 限制 |
保留连接的错误(不断开):
| 错误信息 | 说明 |
|---|---|
Invalid Subject | Subject 格式非法 |
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 是非法的。
分帧与解析
协议解析分两步:
- 读控制行:扫描字节流找到
\r\n,按命令词解析字段 - 读 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 个字节)。
