在上一篇文章中,我們簡單探討了 Zenoh 的網路層。
今天,我們將更往下一層,深入了解 連結層(Link Layer) —— 為 Zenoh 節點之間提供連線的基礎。
Zenoh 橫跨自 資料連結層 一直到 應用層,提供一個統一的通訊框架。在這個系統的核心就是 連結層,它將各種不同的傳輸協定細節抽象化,並為更高層元件提供一致的介面。
連結層展現了 Zenoh 傳輸協定無關(transport-agnostic) 的設計理念 —— 無論你是透過 TCP、QUIC、UDP multicast、WebSocket,甚至是 Unix 管道來通訊,高階 API 都是一致的。
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ (Publishers, Subscribers, Querables, Query Clients) │
├─────────────────────────────────────────────────────────────┤
│ Session Layer │
│ (Session management, Key Expressions) │
├─────────────────────────────────────────────────────────────┤
│ Network Layer │
│ (HAT - Hierarchical Abstraction for Transport) │
├─────────────────────────────────────────────────────────────┤
│ Transport Layer │
│ (Protocol semantics, Batching, Fragmentation) │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Link Layer <---- 我們正在這裡 │ │
│ │ (Transport abstraction, link management) │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ TCP │ TLS │ UDP │ QUIC │ WS │ Serial │ │ │
│ │ Unix │ Pipe │ Vsock │ ... │ │ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Physical Network Layer │
│ (Ethernet, WiFi, Bluetooth, etc.) │
└─────────────────────────────────────────────────────────────┘
Zenoh 的連結層由一系列 內部 crate 與抽象層(位於 io/zenoh-link*
)所組成,用於支援多種傳輸協定,例如 TCP、UDP、QUIC、TLS、WebSocket、Serial、Unix socket、vsock 等。
這種抽象設計讓 Zenoh 與傳輸無關:上層並不需要知道資料是透過 TCP 還是 QUIC 流動,開發者也可以輕鬆新增新的Link類型。每種傳輸都實作在獨立的 crate 內(如 zenoh-link-tcp
, zenoh-link-quic
),但都遵循 zenoh-link-commons
所定義的共通 trait。
👉 若要了解建立在 Link Layer 之上的路由抽象,請參考 HAT (Hierarchical Abstraction for Transport)。
連結層採取 模組化設計,將 Link實作 與 Link管理 分離。
Link Trait 階層
定義於 zenoh-link-commons
,這些 traits(如 LinkUnicastTrait
, LinkMulticastTrait
)規範了所有傳輸類型的共通 API。
Link Manager 階層
Link Manager(如 LinkManagerUnicastTcp
, LinkManagerMulticastUdp
)負責建立、接受與維護連線。
Link 類型
單播 (Unicast):兩個節點之間的點對點通訊
read/write
, close
, get_src/get_dst
, get_mtu
, is_reliable
, is_streamed
多播 (Multicast):一對多的傳輸
LinkMulticastUdp
)Zenoh 支援廣泛的傳輸協定,每個協定都有不同的取捨。
協定 | 可靠性 | 安全性 | 流式傳輸 | 多播 | 認證方式 | 對應 Crate |
---|---|---|---|---|---|---|
TCP | ✅ 可靠 | ❌ 無 | ✅ Stream | ❌ 無 | ❌ 無 | zenoh-link-tcp |
TLS | ✅ 可靠 | ✅ TLS | ✅ Stream | ❌ 無 | ✅ 憑證 | zenoh-link-tls |
UDP | ❌ 不可靠 | ❌ 無 | ❌ Datagram | ✅ 支援 | ❌ 無 | zenoh-link-udp |
QUIC | ✅ 可靠 | ✅ TLS | ✅ Stream | ❌ 無 | ✅ 憑證 | zenoh-link-quic |
QUIC Datagram | ❌ 不可靠 | ✅ TLS | ❌ Datagram | ❌ 無 | ✅ 憑證 | zenoh-link-quic-datagram |
WebSocket | ✅ 可靠 | ❌ 無 | ❌ Message | ❌ 無 | ❌ 無 | zenoh-link-ws |
Unix Socket | ✅ 可靠 | ❌ 無 | ✅ Stream | ❌ 無 | ❌ 無 | zenoh-link-unixsock-stream |
Serial | ❌ 不可靠 | ❌ 無 | ❌ Datagram | ❌ 無 | ❌ 無 | zenoh-link-serial |
Unix Pipe | ✅ 可靠 | ❌ 無 | ✅ Stream | ❌ 無 | ❌ 無 | zenoh-link-unixpipe |
Vsock | ✅ 可靠 | ❌ 無 | ✅ Stream | ❌ 無 | ❌ 無 | zenoh-link-vsock |
每種傳輸協定皆針對其特性實作了專屬的優化與功能:
TCP (zenoh-link-tcp
):可靠、基於串流的傳輸,使用 tokio::TcpStream
。
TCP_NODELAY
以降低延遲,TCP_LINGER
設定優雅關閉超時TLS (zenoh-link-tls
):在 TCP 上提供安全傳輸,使用 X.509 憑證驗證。
UDP (zenoh-link-udp
):不可靠的資料報傳輸,支援 單播 與 多播。
QUIC (zenoh-link-quic
):基於 UDP 的可靠、安全傳輸,支援多路複用串流。
WebSocket (zenoh-link-ws
):基於 TCP 的訊息導向傳輸,使用 tokio-tungstenite
。
Unix Domain Socket 與 Pipe:高效能本機進程間通訊(IPC)。
zenoh-link-unixsock-stream
):基於串流的 IPCzenoh-link-unixpipe
):命名管道,用於跨進程訊息傳遞Serial (zenoh-link-serial
):透過序列埠進行嵌入式系統的直接通訊。
Vsock (zenoh-link-vsock
):虛擬 Socket 介面,用於虛擬機與 Host 之間的通訊。
以下展示 Zenoh 原始碼中一些範例,說明連結層的結構與使用方式。
// 檔案: io/zenoh-link-commons/src/lib.rs
#[derive(Clone, Debug, Serialize, Hash, PartialEq, Eq)]
pub struct Link {
pub src: Locator,
pub dst: Locator,
pub group: Option<Locator>,
pub mtu: BatchSize,
pub is_streamed: bool,
pub interfaces: Vec<String>,
pub auth_identifier: LinkAuthId,
pub priorities: Option<PriorityRange>,
pub reliability: Option<Reliability>,
}
#[async_trait]
pub trait LocatorInspector: Default {
fn protocol(&self) -> &str;
async fn is_multicast(&self, locator: &Locator) -> ZResult<bool>;
fn is_reliable(&self, locator: &Locator) -> ZResult<bool>;
}
此程式碼定義了所有傳輸實作的 共用界面。每個 Link
包含來源/目的定位器、MTU 資訊、串流特性與認證細節。
// 檔案: io/zenoh-link-commons/src/unicast.rs
#[async_trait]
pub trait LinkUnicastTrait: Send + Sync {
fn get_mtu(&self) -> BatchSize;
fn get_src(&self) -> &Locator;
fn get_dst(&self) -> &Locator;
fn is_reliable(&self) -> bool;
fn is_streamed(&self) -> bool;
fn get_interface_names(&self) -> Vec<String>;
fn get_auth_id(&self) -> &LinkAuthId;
async fn write(&self, buffer: &[u8]) -> ZResult<usize>;
async fn write_all(&self, buffer: &[u8]) -> ZResult<()>;
async fn read(&self, buffer: &mut [u8]) -> ZResult<usize>;
async fn read_exact(&self, buffer: &mut [u8]) -> ZResult<()>;
async fn close(&self) -> ZResult<()>;
}
每個單播連結實作(如 TCP、QUIC、TLS 等)都必須實作此 Trait,提供一個 統一介面,不論底層協定為何。
// 檔案: io/zenoh-links/zenoh-link-tcp/src/unicast.rs
pub struct LinkUnicastTcp {
socket: UnsafeCell<TcpStream>,
src_addr: SocketAddr,
src_locator: Locator,
dst_addr: SocketAddr,
dst_locator: Locator,
mtu: BatchSize,
}
impl LinkUnicastTcp {
fn new(socket: TcpStream, src_addr: SocketAddr, dst_addr: SocketAddr) -> Self {
// 設定 TCP NODELAY 選項
socket.set_nodelay(true).unwrap_or_else(|err| {
tracing::warn!("無法設定 NODELAY 選項: {}", err);
});
// 根據 IP 版本計算 MTU
let header = match src_addr.ip() {
std::net::IpAddr::V4(_) => 40, // IPv4 + TCP header
std::net::IpAddr::V6(_) => 60, // IPv6 + TCP header
};
let mtu = TCP_DEFAULT_MTU - header;
// ... 其餘初始化 ...
}
}
每個傳輸協定都會實作具體連結行為,同時遵循共用 Trait。
// 檔案: io/zenoh-links/zenoh-link-udp/src/multicast.rs
pub struct LinkMulticastUdp {
unicast_addr: SocketAddr,
unicast_locator: Locator,
unicast_socket: UdpSocket,
multicast_addr: SocketAddr,
multicast_locator: Locator,
mcast_sock: UdpSocket,
}
#[async_trait]
impl LinkMulticastTrait for LinkMulticastUdp {
async fn close(&self) -> ZResult<()> {
match self.multicast_addr.ip() {
IpAddr::V4(dst_ip4) => {
self.mcast_sock.leave_multicast_v4(dst_ip4, src_ip4)
},
IpAddr::V6(dst_ip6) => {
self.mcast_sock.leave_multicast_v6(&dst_ip6, 0)
}
}
// ... 錯誤處理 ...
}
}
UDP 多播 需要特別處理群組成員資格,且單播與多播操作使用不同的 socket。
// 檔案: io/zenoh-link-commons/src/unicast.rs
pub type LinkManagerUnicast = Arc<dyn LinkManagerUnicastTrait>;
#[async_trait]
pub trait LinkManagerUnicastTrait: Send + Sync {
async fn new_link(&self, endpoint: EndPoint) -> ZResult<LinkUnicast>;
async fn new_listener(&self, endpoint: EndPoint) -> ZResult<Locator>;
async fn del_listener(&self, endpoint: &EndPoint) -> ZResult<()>;
async fn get_listeners(&self) -> Vec<EndPoint>;
async fn get_locators(&self) -> Vec<Locator>;
}
// 範例: TCP 連結管理器實作
impl LinkManagerUnicastTrait for LinkManagerUnicastTcp {
async fn new_link(&self, endpoint: EndPoint) -> ZResult<LinkUnicast> {
let addrs = get_tcp_addrs(endpoint.address()).await?;
let stream = TcpStream::connect(addr).await?;
let link = LinkUnicastTcp::new(stream, src_addr, dst_addr);
Ok(LinkUnicast::from(Arc::new(link)))
}
}
連結管理器負責管理連線生命週期:建立外部連結、設定監聽器以接受進來的連線,以及管理活躍監聽器的登錄。每個傳輸協定都有自己的管理器實作。
順帶一提,這邊使用的sync_trait自Rust 1.75發佈之後其實就已經內建了,可見其歷史的痕跡XD。
傳輸層 透過完善的介面使用 連結層 的抽象概念:
// 檔案:io/zenoh-transport/src/unicast/link.rs
pub(crate) struct TransportLinkUnicast {
pub(crate) link: LinkUnicast,
pub(crate) config: TransportLinkUnicastConfig,
}
impl TransportLinkUnicast {
pub(crate) async fn send(&self, msg: &TransportMessage) -> ZResult<usize> {
// 將訊息序列化並寫入底層連結
let mut link = self.tx();
link.send(msg).await
}
pub(crate) async fn recv(&self) -> ZResult<TransportMessage> {
// 從連結讀取並將訊息反序列化
let mut link = self.rx();
link.recv().await
}
}
傳輸管理器(Transport Manager) 使用連結層來:
這種分離讓傳輸層能專注於Zenoh 協定語義(路由、分段、壅塞控制),而連結層則負責處理網路連線、通訊端配置以及協定特定最佳化的底層細節。
這樣的設計讓 Transport Layer 專注於 Zenoh 協定邏輯,而 Link Layer 則負責底層網路細節。
筆者將在後續的文章中再進一步討論細節,敬請期待!
以下展示 Zenoh 應用中,不同 Link Layer 傳輸方式的實際使用方法:
# 啟動一個 TCP 訂閱者
z_sub --listen tcp/127.0.0.1:7447 --key "demo/example"
# 透過 TCP 連接發佈者
z_pub --connect tcp/127.0.0.1:7447 --key "demo/example" --value "Hello via TCP!"
# QUIC 訂閱者使用 TLS
z_sub --listen quic/127.0.0.1:7447 --key "demo/secure"
# QUIC 發佈者
z_pub --connect quic/127.0.0.1:7447 --key "demo/secure" --value "Secure message"
# Unix socket 訂閱者
z_sub --listen unixsock-stream//tmp/zenoh.sock --key "demo/local"
# Unix socket 發佈者
z_pub --connect unixsock-stream//tmp/zenoh.sock --key "demo/local" --value "Local IPC"
# 同時監聽多種傳輸
z_sub --listen tcp/127.0.0.1:7447 \
--listen udp/224.0.0.1:7448 \
--listen ws/127.0.0.1:8080 \
--key "demo/**"
# 發佈者可以連接任意一種傳輸
z_pub --connect tcp/127.0.0.1:7447 --key "demo/tcp" --value "TCP message"
z_pub --connect udp/224.0.0.1:7448 --key "demo/udp" --value "UDP message"
z_pub --connect ws/127.0.0.1:8080 --key "demo/ws" --value "WebSocket message"
Zenoh Link Layer 的 精髓 在於,切換傳輸方式只需更改 Locator 字串,而應用程式程式碼保持不變!
Zenoh Link Layer 傳輸透過 Cargo 功能標誌(feature flags) 控制,讓你只編譯所需的傳輸方式,減少二進位檔大小並消除不必要的依賴。
# 檔案: zenoh/Cargo.toml
[features]
default = [
"auth_pubkey",
"auth_usrpwd",
"transport_compression",
"transport_multilink",
"transport_quic",
"transport_quic_datagram",
"transport_tcp",
"transport_tls",
"transport_udp",
"transport_unixsock-stream",
"transport_ws",
]
# 個別傳輸功能
transport_tcp = ["zenoh-config/transport_tcp", "zenoh-transport/transport_tcp"]
transport_udp = ["zenoh-transport/transport_udp"]
transport_quic = ["zenoh-transport/transport_quic"]
transport_quic_datagram = ["zenoh-transport/transport_quic_datagram"]
transport_tls = ["zenoh-transport/transport_tls"]
transport_ws = ["zenoh-transport/transport_ws"]
transport_serial = ["zenoh-transport/transport_serial"]
transport_unixpipe = ["zenoh-transport/transport_unixpipe"]
transport_unixsock-stream = ["zenoh-transport/transport_unixsock-stream"]
transport_vsock = ["zenoh-transport/transport_vsock"]
# 額外傳輸功能
transport_compression = ["zenoh-transport/transport_compression"]
transport_multilink = ["zenoh-transport/transport_multilink"]
僅 TCP 的最小建置(適用於嵌入式或資源受限環境):
cargo build --no-default-features --features "transport_tcp"
僅安全傳輸(適用於安全敏感的應用):
cargo build --no-default-features --features "transport_tls,transport_quic"
包含所有傳輸及實驗性功能(適用於開發/測試):
cargo build --features "transport_serial,transport_vsock,transport_unixpipe"
這個 模組化功能系統 使 Zenoh 可以從小型嵌入式設備(如僅使用 transport_serial
)擴展到大型分散式系統(如使用所有可用傳輸)。
想了解如何在 Zenoh 中使用 TLS 或 QUIC,請參考 官方文件。
Zenoh Link Layer 是:
透過隱藏傳輸層複雜性,Link Layer 使 Zenoh 可運作於多種環境 — 從本地進程間通訊到大型分散式網路。