本文以floodsub为例,讨论如何在libp2p-rs上开发新协议,详细代码请查看源码。

实现两个trait

在libp2p-rs中,swarm提供了两个trait:

  • Notifiee用于接收swarm的通知,当有新的连接创建或者连接关闭时,swarm会调用connected()或者disconnected();
  • ProtocolHandler用于读写协议的数据,协议协商成功后,swarm会调用handle()。
cpp
/// 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),致力于提供基于去中心化网络技术实现的分布式存储和分布式计算平台,具有高可用、低功耗和低网络的技术特点,适用于物联网、工业互联网等场景。 公众号:Netwarps