返回文章列表

P2P網路Libp2p架構與節點通訊實作

本文深入解析 P2P 網路核心需求及 libp2p 架構,包含節點身份識別、安全通訊、訊息傳遞、路由及串流多工處理。搭配 Rust 程式碼示範 libp2p 的 Peer ID 生成、多重地址設定、Swarm 網路管理與 mDNS 節點發現機制,逐步引導讀者建構 P2P 應用程式。

網路程式設計 分散式系統

P2P 網路設計需考量節點身份、安全通訊、路由及訊息傳遞等核心需求,libp2p 提供了相對應的解決方案。libp2p 利用公開金鑰密碼學生成唯一的 Peer ID,保障節點身份識別和安全通訊。Multiaddress 格式則整合了網路地址和 Peer ID,方便節點間的互相連線。Swarm 作為 libp2p 的網路管理核心,負責處理節點連線和資料流交換,並透過 Ping 訊息確認連線狀態。此外,mDNS 節點發現機制簡化了本地網路中節點的自動發現流程,使 P2P 應用更易於佈署和使用。

P2P 網路設計的核心需求與 libp2p 網路架構解析

在設計一個 P2P(Peer-to-Peer)網路時,需要滿足多項基本需求,包括對等節點的身份識別、安全通訊、節點路由、訊息傳遞以及串流多工處理。這些需求共同構成了 P2P 網路的基礎架構,使其能夠在去中心化的環境中高效運作。

11.1 P2P 網路的核心需求

P2P 網路需要滿足以下六項核心需求,以確保網路的健全性和高效性:

  1. 節點身份識別(Peer Identity):每個節點需要有一個唯一的身份,以便其他節點能夠識別和連線它。
  2. 安全通訊(Security):節點之間的通訊必須是安全的,以防止第三方攔截或篡改訊息。
  3. 節點路由(Peer Routing):節點需要能夠將訊息路由到其他節點,即使該訊息不是針對自身的。
  4. 訊息傳遞(Messaging):P2P 網路應該支援點對點訊息傳遞和群組訊息傳遞(如發布/訂閱模式)。
  5. 串流多工處理(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);
}

內容解密:

  1. identity::Keypair::generate_ed25519() 生成了一個 ED25519 型別的金鑰對。ED25519 是一種根據橢圓曲線的公鑰系統,廣泛用於 SSH 認證。
  2. PeerId::from(new_key.public()) 將公鑰轉換為 Peer ID。在 libp2p 中,Peer ID 是公鑰的雜湊值,而不是公鑰本身。
  3. 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)
            }
            _ => {}
        }
    }
}

內容解密:

  1. Swarm 元件:Swarm 是 libp2p 中與節點相關聯的網路管理元件。
  2. 資料流交換:使用 libp2p::futures::StreamExt 來交換節點間的資料流。
  3. 虛擬網路行為:使用 DummyBehaviour 作為預設的網路行為。
  4. 傳輸層建立:使用 libp2p::development_transport 建立傳輸層。
  5. Swarm 建立:使用傳輸層、網路行為和對等節點 ID 建立新的 Swarm。
  6. 監聽多重地址:使用 listen_on 方法監聽傳入連線。
  7. 事件輪詢:使用 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),
            _ => {}
        }
    }
}

內容解密:

  1. Ping 網路行為:使用 Ping 網路行為來啟用節點間的 Ping 訊息交換。
  2. 遠端節點連線:使用命令列引數傳入遠端節點的多重地址,並建立連線。
  3. 事件處理:處理接收到的 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);
                }
            }
            _ => {}
        }
    }
}

內容解密:

  1. 生成PeerId:使用identity::Keypair::generate_ed25519()生成一個新的PeerId。
  2. 建立transport:使用libp2p::development_transport(id_keys).await?建立一個transport。
  3. 建立mDNS網路行為:使用Mdns::new(MdnsConfig::default()).await?建立一個mDNS網路行為。
  4. 建立Swarm:使用Swarm::new(transport, behaviour, peer_id)建立一個Swarm。
  5. 監聽本地地址:使用swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?監聽本地地址。
  6. 處理事件:使用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

這表明第二個節點已經發現了第一個節點。