在libp2p-rs上開發新協議

本文以floodsub為例,討論如何在libp2p-rs上開發新協議,詳細代碼請查看源碼。

實現兩個trait

在libp2p-rs中,swarm提供了兩個trait:

  • Notifiee用于接收swarm的通知,當有新的連接創建或者連接關閉時,swarm會調用connected()或者disconnected();
  • ProtocolHandler用于讀寫協議的數據,協議協商成功后,swarm會調用handle()。
/// Notifiee is an trait for an object wishing to receive notifications from swarm
pub trait Notifiee {
    /// called when a connection opened
    fn connected(&mut self, _conn: &mut Connection) {}
    /// called when a connection closed
    fn disconnected(&mut self, _conn: &mut Connection) {}
}

/// Common trait for upgrades that can be applied on inbound substreams, outbound substreams,
/// or both.
/// Possible upgrade on a connection or substream.
#[async_trait]
pub trait ProtocolHandler: UpgradeInfo + Notifiee {
    /// After we have determined that the remote supports one of the protocols we support, this
    /// method is called to start handling the inbound. Swarm will start invoking this method
    /// in a newly spawned task.
    ///
    /// The `info` is the identifier of the protocol, as produced by `protocol_info`.
    async fn handle(&mut self, stream: Substream, info: <Self as UpgradeInfo>::Info) -> Result<(), Box<dyn Error>>;
    /// This is to provide a clone method for the trait object.
    fn box_clone(&self) -> IProtocolHandler;
}

floodsub handler實現Notifiee和ProtocolHandler

#[derive(Clone)]
pub struct Handler {
    incoming_tx: mpsc::UnboundedSender<RPC>,
    new_peer: mpsc::UnboundedSender<PeerEvent>,
}

impl Handler {
    pub(crate) fn new(incoming_tx: mpsc::UnboundedSender<RPC>, new_peer: mpsc::UnboundedSender<PeerEvent>) -> Self {
        Handler { incoming_tx, new_peer }
    }
}

impl UpgradeInfo for Handler {
    type Info = &'static [u8];

    fn protocol_info(&self) -> Vec<Self::Info> {
        vec![FLOOD_SUB_ID]
    }
}

impl Notifiee for Handler {
    fn connected(&mut self, conn: &mut Connection) {
        let peer_id = conn.remote_peer();
        let mut new_peers = self.new_peer.clone();
        task::spawn(async move {
            let _ = new_peers.send(PeerEvent::NewPeer(peer_id)).await;
        });
    }
}

#[async_trait]
impl ProtocolHandler for Handler {
    async fn handle(&mut self, mut stream: Substream, _info: <Self as UpgradeInfo>::Info) -> Result<(), Box<dyn Error>> {
        loop {
            /* recv, decode and send to msg process mainloop */
            self.incoming_tx.send(rpc).await.map_err(|_| FloodsubDecodeError::ProtocolExit)?;
        }
    }

    fn box_clone(&self) -> IProtocolHandler {
        Box::new(self.clone())
    }
}

注冊到swarm

let floodsub = FloodSub::new(FloodsubConfig::new(local_peer_id));
let handler = floodsub.handler();

let mut swarm = Swarm::new(local_key.public()).with_protocol(Box::new(handler))

還需要做什么

簡單的協議,比如echo,那么所有事情都在ProtocolHandler.handle()中處理即可,到這里就結束了。

稍微復雜的協議,比如floodsub,最好將swarm的通知和收到的數據,發送到消息處理主循環進行處理,實時更新狀態;

impl floodsub {
    pub fn start(mut self, control: Swarm_Control) {
        self.control = Some(control);
    
        // well, self 'move' explicitly,
        let mut floodsub = self;
        task::spawn(async move {
            let _ = floodsub.process_loop().await;
        });
    }

    /// Message Process Loop.
    pub async fn process_loop(&mut self) -> Result<()> {
        loop {
            select! {
                cmd = self.peer_rx.next() => {
                    self.handle_peer_event(cmd).await;
                }
                rpc = self.incoming_rx.next() => {
                    self.handle_incoming_rpc(rpc).await?;
                }
                cmd = self.control_rx.next() => {
                    self.on_control_command(cmd).await?;
                }
                sub = self.cancel_rx.next() => {
                    self.un_subscribe(sub).await?;
                }
            }
        }
    }
}

從上面可以看到,floodsub消息處理主循環運行在一個task里面,start()時需要將self傳遞進去,因此后續的發布訂閱等操作只能通過channel發消息,這就是control和handler包裹channel的原因。

#[derive(Clone)]
pub struct Control {
    config: FloodsubConfig,
    control_sender: mpsc::UnboundedSender<ControlCommand>,
}

impl Control {
    /// Subscribe to messages on a given topic.
    pub async fn subscribe(&mut self, topic: Topic) -> Option<Subscription> {
        let (tx, rx) = oneshot::channel();
        self.control_sender
            .send(ControlCommand::Subscribe(topic, tx))
            .await
            .expect("control send subscribe");
        rx.await.expect("Subscribe")
    }
}

當新的連接創建時,floodsub會主動創建流,協商通過后向對方發送本節點感興趣的topic。因此這里需要swarm的control。

self.control.as_mut().unwrap().new_stream(pid, vec![FLOOD_SUB_ID]).await;

總結

在libp2p-rs上面開發簡單的協議,只需要兩步,對于稍微復雜的協議,需要handler和control這類包裹channel的結構,將消息發送到協議消息處理主循環,以驅動整個協議的運轉,完成特定的功能。

Netwarps 由國內資深的云計算和分布式技術開發團隊組成,該團隊在金融、電力、通信及互聯網行業有非常豐富的落地經驗。Netwarps 目前在深圳、北京均設立了研發中心,團隊規模30+,其中大部分為具備十年以上開發經驗的技術人員,分別來自互聯網、金融、云計算、區塊鏈以及科研機構等專業領域。
Netwarps 專注于安全存儲技術產品的研發與應用,主要產品有去中心化文件系統(DFS)、去中心化計算平臺(DCP),致力于提供基于去中心化網絡技術實現的分布式存儲和分布式計算平臺,具有高可用、低功耗和低網絡的技術特點,適用于物聯網、工業互聯網等場景。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容