P2P 網路設計需考量節點身份、安全通訊、路由及訊息傳遞等核心需求,libp2p 提供了相對應的解決方案。libp2p 利用公開金鑰密碼學生成唯一的 Peer ID,保障節點身份識別和安全通訊。Multiaddress 格式則整合了網路地址和 Peer ID,方便節點間的互相連線。Swarm 作為 libp2p 的網路管理核心,負責處理節點連線和資料流交換,並透過 Ping 訊息確認連線狀態。此外,mDNS 節點發現機制簡化了本地網路中節點的自動發現流程,使 P2P 應用更易於佈署和使用。
P2P 網路設計的核心需求與 libp2p 網路架構解析
在設計一個 P2P(Peer-to-Peer)網路時,需要滿足多項基本需求,包括對等節點的身份識別、安全通訊、節點路由、訊息傳遞以及串流多工處理。這些需求共同構成了 P2P 網路的基礎架構,使其能夠在去中心化的環境中高效運作。
11.1 P2P 網路的核心需求
P2P 網路需要滿足以下六項核心需求,以確保網路的健全性和高效性:
- 節點身份識別(Peer Identity):每個節點需要有一個唯一的身份,以便其他節點能夠識別和連線它。
- 安全通訊(Security):節點之間的通訊必須是安全的,以防止第三方攔截或篡改訊息。
- 節點路由(Peer Routing):節點需要能夠將訊息路由到其他節點,即使該訊息不是針對自身的。
- 訊息傳遞(Messaging):P2P 網路應該支援點對點訊息傳遞和群組訊息傳遞(如發布/訂閱模式)。
- 串流多工處理(Stream Multiplexing):支援在單一通訊鏈路上傳輸多個資訊流,以實作與多個節點的平行通訊。
11.1.1 傳輸層(Transport)
P2P 網路中的每個節點應該能夠支援多種傳輸協定,如 TCP/IP、UDP、HTTP 和 QUIC,以適應網路中多樣化的節點。
11.1.2 節點身份識別(Peer Identity)
與傳統的 Web 開發不同,P2P 網路中的節點需要使用公開金鑰密碼學(asymmetric public key cryptography)來建立安全的通訊管道。每個節點的身份由其公開金鑰的密碼雜湊值表示,稱為 PeerId。
// 使用 libp2p 建立 PeerId 的範例程式碼
use libp2p::identity::Keypair;
let local_key = Keypair::generate_ed25519();
let local_peer_id = local_key.public().into_peer_id();
println!("Local Peer ID: {:?}", local_peer_id);
內容解密:
此段程式碼展示瞭如何使用 libp2p 函式庫生成一個新的 PeerId。首先,我們使用 Keypair::generate_ed25519() 生成一個 Ed25519 金鑰對,然後從中提取公鑰並轉換為 PeerId。這裡的 PeerId 是根據公鑰生成的唯一識別符號,用於在 P2P 網路中識別節點。
11.1.3 安全通訊(Security)
除了使用公開金鑰密碼學建立安全的通訊管道外,節點還需要實作授權框架,以規範哪些操作可以由哪些節點執行。此外,還需要防範網路層級的安全威脅,如 Sybil 攻擊和 Eclipse 攻擊。
11.1.4 節點路由(Peer Routing)
在一個動態變化的 P2P 網路中,節點需要維護一個路由表,以找到其他節點並進行通訊。節點路由機制使得節點能夠將不是針對自身的訊息轉發到目標節點。
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333
title 11.1.4 節點路由(Peer Routing)
rectangle "11.1.4 節點路由(Peer Routing)" as n1
rectangle "實作" as n2
rectangle "應用" as n3
n1 --> n2
n2 --> n3
@enduml
圖表翻譯: 此圖表展示了一個簡單的節點路由過程。節點 A 傳送一個訊息給節點 B,節點 B 將該訊息轉發給節點 C,最終由節點 C 將訊息送達目標節點 D。這種機制使得 P2P 網路中的節點能夠協同工作,將訊息送達正確的接收者。
11.2 libp2p 網路的核心架構
libp2p 是一個模組化的 P2P 網路函式庫,提供了豐富的功能和協定規範,使得開發 P2P 應用變得更加容易。其核心模組包括:
- 傳輸層(Transport):負責節點之間的資料傳輸。
- 身份識別(Identity):根據公開金鑰密碼學為每個節點生成唯一的
PeerId。 - 安全通訊(Security):支援加密的通訊管道,防止第三方攔截或篡改訊息。
- 節點發現(Peer Discovery):使節點能夠在網路中發現並連線其他節點。
- 內容發現(Content Discovery):允許節點在不知道內容具體位於哪個節點的情況下取得內容。
使用 libp2p 可以大大簡化 P2P 應用的開發過程,並提供穩健的網路基礎設施。未來,我們將探討如何使用 libp2p 編寫非同步 Rust 程式碼,以實作更複雜的 P2P 網路功能。
libp2p 技術深度解析:P2P 網路的核心元件
在 P2P 網路的世界中,libp2p 扮演著至關重要的角色。它提供了一系列的工具和協定,使得開發者能夠建立高效、穩定的 P2P 應用程式。在本章中,我們將探討 libp2p 的核心元件,並透過 Rust 程式碼範例來展示其實際應用。
11.2.1 Peer IDs 與金鑰對
在 P2P 網路中,每個節點都需要一個唯一的身份標識,即 Peer ID。Peer ID 是由節點的公鑰經過雜湊運算得到的。要生成 Peer ID,首先需要生成一個金鑰對。金鑰對由私鑰和公鑰組成,私鑰用於簽署訊息,而公鑰則用於生成 Peer ID。
程式碼範例:生成 Peer ID 與金鑰對
use libp2p::{identity, PeerId};
#[tokio::main]
async fn main() {
let new_key = identity::Keypair::generate_ed25519();
let new_peer_id = PeerId::from(new_key.public());
println!("New peer id: {:?}", new_peer_id);
}
內容解密:
identity::Keypair::generate_ed25519()生成了一個 ED25519 型別的金鑰對。ED25519 是一種根據橢圓曲線的公鑰系統,廣泛用於 SSH 認證。PeerId::from(new_key.public())將公鑰轉換為 Peer ID。在 libp2p 中,Peer ID 是公鑰的雜湊值,而不是公鑰本身。println!宏用於列印生成的 Peer ID。
11.2.2 多重地址(Multiaddresses)
在 P2P 網路中,節點需要分享其聯絡資訊,即多重地址。多重地址包含了節點的網路地址和 Peer ID。網路地址描述了節點如何被存取,例如透過 TCP 或 WebSockets。
多重地址的組成
- Peer ID 部分:
/p2p/12D3KooWBu3fmjZgSMLkQ2p1DG35UmEayYBrhsk6WEe1xco1JFbV - 網路地址部分:
/ip4/192.158.1.23/tcp/1234
完整的多重地址是兩者的結合:/ip4/192.158.1.23/tcp/1234/p2p/12D3KooWBu3fmjZgSMLkQ2p1DG35UmEayYBrhsk6WEe1xco1JFbV
11.2.3 Swarm 與網路行為
Swarm 是 libp2p 中的網路管理模組,負責維護節點的所有活躍和待定連線,以及管理所有已開啟的子流狀態。
Swarm 的關鍵功能
- 管理與遠端節點的連線
- 管理子流的狀態
Swarm 在 P2P 網路中的角色至關重要,它確保了節點之間的通訊順暢和網路的穩定性。
libp2p 網路管理與節點通訊實作
在前一章節中,我們探討了 P2P 網路的基本概念與 libp2p 的基本架構。現在,讓我們進一步深入 libp2p 的 Swarm 網路管理元件,並實作節點間的通訊。
Swarm 網路管理元件
Swarm 是 libp2p 中的核心元件,負責管理節點的網路行為和連線。圖 11.5 展示了 Swarm 的結構和上下文,接下來我們將詳細說明其運作原理。
程式碼實作:建立 Swarm 網路管理器
首先,建立一個新的 src/bin/iter2.rs 檔案,並加入以下程式碼:
use libp2p::swarm::{DummyBehaviour, Swarm, SwarmEvent};
use libp2p::futures::StreamExt;
use libp2p::{identity, PeerId};
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let new_key = identity::Keypair::generate_ed25519();
let new_peer_id = PeerId::from(new_key.public());
println!("local peer id is: {:?}", new_peer_id);
let behaviour = DummyBehaviour::default();
let transport = libp2p::development_transport(new_key).await?;
let mut swarm = Swarm::new(transport, behaviour, new_peer_id);
swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
loop {
match swarm.select_next_some().await {
SwarmEvent::NewListenAddr { address, .. } => {
println!("Listening on local address {:?}", address)
}
_ => {}
}
}
}
內容解密:
- Swarm 元件:Swarm 是 libp2p 中與節點相關聯的網路管理元件。
- 資料流交換:使用
libp2p::futures::StreamExt來交換節點間的資料流。 - 虛擬網路行為:使用
DummyBehaviour作為預設的網路行為。 - 傳輸層建立:使用
libp2p::development_transport建立傳輸層。 - Swarm 建立:使用傳輸層、網路行為和對等節點 ID 建立新的 Swarm。
- 監聽多重地址:使用
listen_on方法監聽傳入連線。 - 事件輪詢:使用
select_next_some方法持續輪詢事件。
執行與測試
建立兩個終端機視窗,分別執行以下命令:
cargo run --bin iter2
觀察輸出結果,你將看到兩個節點的本地地址和對等節點 ID。
增強功能:交換 Ping 命令
建立一個新的 src/bin/iter3.rs 檔案,並加入以下程式碼:
use libp2p::swarm::{Swarm, SwarmEvent};
use libp2p::futures::StreamExt;
use libp2p::ping::{Ping, PingConfig};
use libp2p::{identity, Multiaddr, PeerId};
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let new_key = identity::Keypair::generate_ed25519();
let new_peer_id = PeerId::from(new_key.public());
println!("local peer id is: {:?}", new_peer_id);
let transport = libp2p::development_transport(new_key).await?;
let behaviour = Ping::new(PingConfig::new().with_keep_alive(true));
let mut swarm = Swarm::new(transport, behaviour, new_peer_id);
swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
if let Some(remote_peer) = std::env::args().nth(1) {
let remote_peer_multiaddr: Multiaddr = remote_peer.parse()?;
swarm.dial(remote_peer_multiaddr)?;
println!("Dialed remote peer: {:?}", remote_peer);
}
loop {
match swarm.select_next_some().await {
SwarmEvent::NewListenAddr { address, .. } => {
println!("Listening on local address {:?}", address)
}
SwarmEvent::Behaviour(event) => println!("Event received from peer is {:?}", event),
_ => {}
}
}
}
內容解密:
- Ping 網路行為:使用
Ping網路行為來啟用節點間的 Ping 訊息交換。 - 遠端節點連線:使用命令列引數傳入遠端節點的多重地址,並建立連線。
- 事件處理:處理接收到的 Ping 事件並輸出到終端機。
探索P2P網路中的節點發現機制
在前一章節中,我們探討了兩個P2P節點之間如何交換ping訊息。在實際的P2P網路中,節點會動態地加入和離開網路,因此節點發現是P2P網路中的一個重要功能。本章節將介紹如何使用libp2p實作節點發現。
使用mDNS實作節點發現
Multicast DNS(mDNS)是一種由RFC 6762定義的協定,用於解析主機名稱為IP地址。libp2p中的mDNS網路行為可以自動發現本地網路上的其他libp2p節點。
程式碼實作
以下是使用libp2p實作節點發現的程式碼範例(src/bin/iter4.rs):
use libp2p::{
futures::StreamExt,
identity,
mdns::{Mdns, MdnsConfig, MdnsEvent},
swarm::{Swarm, SwarmEvent},
PeerId,
};
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let id_keys = identity::Keypair::generate_ed25519();
let peer_id = PeerId::from(id_keys.public());
println!("Local peer id: {:?}", peer_id);
let transport = libp2p::development_transport(id_keys).await?;
let behaviour = Mdns::new(MdnsConfig::default()).await?;
let mut swarm = Swarm::new(transport, behaviour, peer_id);
swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
loop {
match swarm.select_next_some().await {
SwarmEvent::NewListenAddr { address, .. } => {
println!("Listening on local address {:?}", address)
}
SwarmEvent::Behaviour(MdnsEvent::Discovered(peers)) => {
for (peer, addr) in peers {
println!("discovered {} {}", peer, addr);
}
}
SwarmEvent::Behaviour(MdnsEvent::Expired(expired)) => {
for (peer, addr) in expired {
println!("expired {} {}", peer, addr);
}
}
_ => {}
}
}
}
內容解密:
- 生成PeerId:使用
identity::Keypair::generate_ed25519()生成一個新的PeerId。 - 建立transport:使用
libp2p::development_transport(id_keys).await?建立一個transport。 - 建立mDNS網路行為:使用
Mdns::new(MdnsConfig::default()).await?建立一個mDNS網路行為。 - 建立Swarm:使用
Swarm::new(transport, behaviour, peer_id)建立一個Swarm。 - 監聽本地地址:使用
swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?監聽本地地址。 - 處理事件:使用
swarm.select_next_some().await處理事件,包括新節點的發現和過期節點的移除。
執行結果
執行上述程式碼,可以看到以下輸出:
Local peer id: PeerId("12D3KooWNgYbVg8ZyJ4ict2N1hdJLKoydB5sTqwiWN2SHtC3HwWt")
Listening on local address "/ip4/127.0.0.1/tcp/50960"
Listening on local address "/ip4/192.168.1.74/tcp/50960"
開啟另一個終端,執行相同的程式碼,可以看到以下輸出:
Local peer id: PeerId("12D3KooWCVVb2EyxB1WdAcLeMuyaJ7nnfUCq45YNNuFYcZPGBY1f")
Listening on local address "/ip4/127.0.0.1/tcp/50967"
Listening on local address "/ip4/192.168.1.74/tcp/50967"
discovered 12D3KooWNgYbVg8ZyJ4ict2N1hdJLKoydB5sTqwiWN2SHtC3HwWt /ip4/192.168.1.74/tcp/50960
discovered 12D3KooWNgYbVg8ZyJ4ict2N1hdJLKoydB5sTqwiWN2SHtC3HwWt /ip4/127.0.0.1/tcp/50960
這表明第二個節點已經發現了第一個節點。