iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0
Rust

30 天玩轉 Zenoh:Rust 助力物聯網、機器人與自駕的高速通訊系列 第 23

Day 23: 認識 Zenoh Protocol的架構與實作

  • 分享至 

  • xImage
  •  

認識 Zenoh Protocol的架構與實作

繼上一篇 Day 22: 使用 Rust 剖析 Zenoh:深入了解 Wireshark 插件 zenoh-dissector 之後,今天我們來深入探討一下 Protocol 細節吧!

Zenoh Protocol是一種先進的 pub/sub 及查詢/回復訊息協定,設計上能同時高效支援資源受限的物聯網裝置與高通量資料中心應用。本文將探討其分層架構與實作細節。

Protocol 總覽

Zenoh Protocol以清楚分層為核心設計,兼顧分工與運作效率。Protocol 包含兩層:

  • 傳輸層(Transport Layer):session 管理、可靠性、訊息包裝
  • 網路層(Network Layer):核心 pub/sub 與查詢/回復功能(包括payload等)

每一層皆有獨立職責與wire format,但又能協作實現 Zenoh 的訊息能力。

雙層架構

傳輸層:基礎與 Session 管理

傳輸層構成所有 Zenoh 通訊的基礎,通過 TransportMessage 結構處理連線生命週期、可靠性、訊息包裝等。

訊息型別

Session 管理訊息:

  • **InitSyn/InitAck **:在特定locator進行 Session 啟動握手

    • 發起端送出 INIT(A flag=0),接收端用 A flag=1 回應
    • 交換Protocol版本、peer 類型(whatami)、批次大小
    • 包含 Zenoh 識別碼(ZID)與驗證參數
  • OpenSyn/OpenAck (0x02):完成成功 INIT 後 final link 初始化

    • 建立 lease 期(依 T flag 決定秒/ms)
    • 設置初始序號與驗證 cookie
    • 完成 unicast session 建立
  • Close (0x03):結束 session 或 link

    • S flag: S=1 關閉全部 session,S=0 關閉僅 link
    • 支援原因碼:GENERIC、UNSUPPORTED、INVALID、MAX_SESSIONS、MAX_LINKS、EXPIRED、UNRESPONSIVE、CONNECTION_TO_SELF
  • KeepAlive (0x04):心跳機制

    • 週期送出防止 lease 到期
    • 若區間內有資料傳輸則可略過
  • Join (0x07):router-to-router 聯邦,多播通訊用

數據框架訊息:

  • Frame (0x05):主要的訊息包裝型別

    • 可聚合多個已序列化之 NetworkMessage
    • 總長度不得超過 2^16-1 字節且低於 link MTU
    • 包含可靠性標誌(R)及序列號
  • Fragment (0x06):處理超大訊息

    • 將超過批次或 MTU 的 NetworkMessage 分片
    • M flag 標記「還有後續分片」
    • 各片段維持可靠性語意

批次大小設定

不同通訊型態,transport message 限制不同:

pub mod batch_size {
    pub const UNICAST: BatchSize = 65535;     // 單播最大
    pub const MULTICAST: BatchSize = 8192;    // 多播 8KB
}

網路層:核心訊息邏輯

網路層於 Frame 訊息 payload 實現核心 pub/sub 與查詢/回復功能。網路訊息用 0x19-0x1f ID 空間,避免與傳輸 ID 衝突。

訊息型別

  • Push (0x1d):發佈資料訊息

    • 搭載 pub/sub 的 Put/Del 操作
    • key expression wire format可帶 name/suffix(N flag 控制)
    • 支援 mapping flag(M),決定 sender/receiver 之 key 解析
    • 包含 QoS 擴充、時間戳與節點識別資訊
  • Request (0x1c):發出查詢請求

    • 有唯一 32 bits RequestId 供回應對應
    • 包含 key expr 範圍與 suffix
    • 支援參數與合併選項等 extension
  • Response (0x1b):個別查詢回應

    • RequestId 連結(32 bit,協商於 session)
    • key 表達格式與 Request 相同
    • ResponseBody,實際資料
  • ResponseFinal (0x1a):串流查詢結束標記

    • 標記多回應查詢完成
    • 與對應 Request 共用 RequestId
  • Interest (0x19):發現暨訂閱管理

    • 請求現有+未來宣告
    • 支援 Current、Future、CurrentFuture 多種查詢模式
    • 用 Interest ID 與日後 Declare 關聯
    • 用於發現既有訂閱、可查詢項等
  • Declare (0x1e):資源管理

    • (反)註冊訂閱者、可查詢、key expr 映射
    • 對 Interest 自動回傳(I flag 控制)
    • 根據資源型態帶不同 payload

Key Expression 編碼

網路訊息具高效 key expr 編碼方式:

  • 預宣告表達式:僅 key scope ID(最高效)
  • 完整表達式:有 scope 及 suffix(最彈性)
  • 映射模式:sender/receiver 皆可解析 key

訊息有效資料:數據與語意

訊息有效資料定義於網路訊息內:

Push 訊息 Payload (PushBody):

  • Put:發佈資料,含時間戳、編碼、payload (ZBuf)
  • Del:Key 刪除通知(分散狀態管理)

Request 訊息 Payload (RequestBody):

  • Query:查詢參數、合併選項與設定

Response 訊息 Payload (ResponseBody):

  • Reply:查詢成功回應,載帶 PushBody 數據
  • Err:查詢失敗錯誤與碼

核心資料結構

所有層都常見的型別:

  • WireExpr:key expr 線路(string 或預先宣告之整數 ID,利於資源優化)
  • ID 型別:如 RequestIdExprIdSubscriberId 管理訊息關聯與資源
  • ZSlice/ZBuf:零拷貝 buffer,序列化/去序列化時免配置
  • Extensions:如 ext_* metadata,供 QoS、timestamp、路由等
  • Reliability Types:如 CongestionControlPriorityReliability 枚舉提供 QoS 設定

編碼實作

Trait 架構

zenoh-codec crate 提供簡潔的 trait 為主的序列化設計:

// 讀 operation - 反序列化
pub trait RCodec<Message, Buffer> {
    type Error;
    fn read(self, buffer: Buffer) -> Result<Message, Self::Error>;
}

// 寫 operation - 序列化
pub trait WCodec<Message, Buffer> {
    type Output;
    fn write(self, buffer: Buffer, message: Message) -> Self::Output;
}

// 長度計算 - 缓冲區預分配
pub trait LCodec<Message> {
    fn w_len(self, message: Message) -> usize;
}

Zenoh080 struct 實作這些 trait,對應Protocol版本。

序列化流程

codec 流程非常有效:

  • 標頭決定解析分支:首字節決定型別與路由
  • 訊息分派處理:不同型別交由專屬 codec 處理
  • flag 差異:如 InitSyn/InitAck 可由旗標區分
  • 零拷貝操作:用高效 buffer 函式不產生多餘配置

設計原則

codec 架構採精益求精原則:

  • 模組化:每訊息型別獨立解析
  • 型別安全:Rust trait 系統強制正確
  • 高效能:單循環解析,零拷貝
  • 可擴展:新型別與Protocol版本易加入

訊息流程樣式

Session 建立流程

完整建立 session 需多重 message:

A                   B
|      InitSyn      |
|------------------>| A=0, version, whatami, ZID
|                   |
|      InitAck      |
|<------------------| A=1, accepted parameters
|                   |
|      OpenSyn      |
|------------------>| A=0, lease period, initial_sn, cookie
|                   |
|      OpenAck      |
|<------------------| A=1, lease period, initial_sn
|                   |
|     Frame(...)    |
|<----------------->| 雙向數據傳輸

分片處理流程

大訊息需分片:

A                   B
| Fragment(M=1, #1) |
|------------------>| 序列起始片段
| Fragment(M=1, #2) |
|------------------>| 中間片段
| Fragment(M=1, #3) |
|------------------>| 另一中片段
| Fragment(M=0, #4) |
|------------------>| 最後片段 (M=0)
|                   | B 組回完整訊息

Interest 主動查詢

Zenoh 用 Interest 進行發現:

A                    B
|     Interest       |
|------------------->| mode=Current, keyexpr="sensors/*"
|                    |
| Declare(Subscriber)|
|<-------------------| interest_id=456, "sensors/temp"
| Declare(Subscriber)|
|<-------------------| interest_id=456, "sensors/pressure"
|   DeclareFinal     |
|<-------------------| interest_id=456, end of declarations

查詢/回應模式

多回查詢顯示對應關聯:

A                   B
| Request(Query)    |
|------------------>| RequestId=123, keyexpr="db/*"
|                   |
| Response(Reply)   |
|<------------------| RequestId=123, "db/users" data
| Response(Reply)   |
|<------------------| RequestId=123, "db/products" data
|  ResponseFinal    |
|<------------------| RequestId=123, end of results

實務案例

發佈/訂閱應用資料流

發佈端(出自 examples/z_pub.rs):

// 應用程式碼
let publisher = session.declare_publisher(&key_expr).await.unwrap();
publisher
    .put(buf)
    .encoding(Encoding::TEXT_PLAIN)
    .attachment(attachment.clone())
    .await
    .unwrap();

Protocol堆疊如下:

  1. 傳輸層:包 Frame 訊息 (ID 0x05)
  2. 網路層:Push 訊息 (ID 0x1d),Put payload 載帶 key expr、編碼與資料

訂閱端(出自 examples/z_sub.rs):

// 應用程式碼
let subscriber = session.declare_subscriber(&key_expr).await.unwrap();
while let Ok(sample) = subscriber.recv_async().await {
    let payload = sample.payload().try_to_string().unwrap();
    println!("Received: {}", payload);
}

對應流程:

  1. 傳輸層:接收 Frame
  2. 網路層:取出 Push/Put,交應用層處理資料

結論

Zenoh Protocol展示了如何以分層設計與 trait 為基礎序列化,打造橫跨 diverse 場景的高效 robust 訊息系統。重點亮點:

  • 清楚分層架構:運輸/網路層嚴格分責
  • 高效序列化:極低成本編解碼
  • 零拷貝最佳化:底層的ZSliceZBuf 杜絕不必要的記憶體配置
  • 型別安全實作:Rust trait 編譯期正確性檢查
  • 可擴展設計:模組架構支援未來Protocol演化

上一篇
Day 22: 使用 Rust 剖析 Zenoh:深入了解 Wireshark 插件 zenoh-dissector
下一篇
Day 24: Zenoh 在機器人系統的應用全景 Part 1 - ROS1 與 zenoh-plugin-ros1:傳統機器人網路的新橋梁
系列文
30 天玩轉 Zenoh:Rust 助力物聯網、機器人與自駕的高速通訊24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言