go-libp2p 入門之 PingService example

Install go-libp2p

go get -u -d github.com/libp2p/go-libp2p/...
cd $GOPATH/src/github.com/libp2p/go-libp2p
make
make deps

官網給出了安裝說明:

go get 中 -u 表示下載最新版的依賴,-d 表示只下載不安裝,當然你需要先安裝 golang 才能使用 go get ,這裏推薦安裝 1.10.x 版本

golang安裝包 : https://studygolang.com/dl

make 時需要下載 gx 組件,如果想直接用 make 來下載最好還是先“科學上網”,如果直接在 github 上下載安裝 gx 則無需擔心 “科學上網問題”

make deps 是要下載全部依賴了,這裏會用 gx 來完成下載,第一次用時你會發現他要到 ipfs.io 去 get 依賴包,但是即便“科學上網”了也還是無法訪問,其實只要在本地先啓動一個 ipfs 節點就可以了,他會優先在本地的 ipfs 節點獲取資源

IPFS 的下載和安裝 : https://ipfs.io/docs/install/

啓動命令: ipfs daemon

然後再去 make deps 經過漫長的等待,即可完成依賴的安裝

很多小夥伴是不是都在這一步放棄了 go-libp2p 呀?其實我也挺討厭這個 gx 的,不如 govendor 用着舒服,不幸的是想繼續探索 go-libp2p 你就必須要掌握 gx 的用法,並且要去習慣使用 gx

gx教程:https://github.com/whyrusleeping/gx

安裝還是比較容易的,一定要粗略的看一遍說明書再去看代碼和例子,否則很難發現它的美

libp2p 說明書: https://github.com/libp2p/specs

Example

PingService

使用 libp2p 做服務是我們學習的目的,通過 PingService 來入門是個不錯的選擇,簡單看一下 PingService 的代碼,你會發現實現一個服務非常簡單, host.Host 接口是核心

PingService : http://github.com/libp2p/go-libp2p/p2p/protocol/ping/ping.go

......
const ID = "/ipfs/ping/1.0.0"

type PingService struct {
    Host host.Host
}

func NewPingService(h host.Host) *PingService {
    ps := &PingService{h}
    h.SetStreamHandler(ID, ps.PingHandler)
    return ps
}

func (p *PingService) PingHandler(s inet.Stream) {
    ......
}
func (ps *PingService) Ping(ctx context.Context, p peer.ID) (<-chan time.Duration, error) {
    s, err := ps.Host.NewStream(ctx, p, ID)
    ......
}
......

這個代碼片段演示如何通過 host.Host 接口構建一個服務,Host 接口描述如下:

Host is an object participating in a p2p network, which implements protocols or provides services. It handles requests like a Server, and issues requests like a Client. It is called Host because it is both Server and Client (and Peer may be confusing).

在文章的結尾貼出了 Host 接口的代碼,這個服務主要使用下面這兩個方法

  • SetStreamHandler(pid protocol.ID, handler inet.StreamHandler)
    首先通過 Host.SetStreamHandler 來爲 "/ipfs/ping/1.0.0" 協議指定 callback 方法,這裏指定了 PingHandler 方法,參數中的 inet.Stream 接口對 io 接口做了擴展,這裏要做的就是當請求到來時對 s 的輸入流做讀操作

  • NewStream(ctx context.Context, p peer.ID, pids ...protocol.ID) (inet.Stream, error)
    在 Ping 方法被調用時首先通過 NewStream 打開了一個流,在 p2p 網絡中打開流都是有目標的,這個目標就是 peer.ID ,因爲這個 peer 上可能會註冊很多服務,所以也要指明服務ID,就是參數中的 protocol.ID ,剩下的工作就是向 s 的輸出流中寫入數據包了。

調用 PingService

看上去調用一個服務比編寫一個服務複雜一些,
首先NewHost就是一個非常複雜的操作,參數中的 Network 接口我們用 Swarm 來填充,那麼 swarm 又是什麼呢?它在這裏是 Network 接口的一個實現,
Host 接口有兩個實現,我們用 BasicHost 來實例化 Host 接口.

下面提供了一個完整的調用 pingService 的例子
https://github.com/cc14514/go-libp2p-example/blob/master/examples/host/main.go

想運行這份樣例代碼,首先要把 import 中的這些依賴包搞定
如果你的 gopath 中有 ipfs 的代碼,還有 go-libp2p 的代碼,並且都執行過 make deps 那麼你想自己寫一個樣例是會遇到一些小麻煩的,在導入每個依賴包時都有2-3個可選的,因爲要導入的依賴很多所以很久也找不到那個正確的組合。

這裏給一個簡單的解決辦法

  1. 啓動 ipfs
# 啓動ipfs
ipfs daemon
# 檢查peers,沒有 peers 時是不可用的
ipfs swarm peers
  1. 用 gx 重新加載依賴包
# 備份 gx 目錄
mv $GOPATH/src/gx/ipfs $GOPATH/src/gx/ipfs.bak
# 進入 example 目錄
cd $GOPATH/src/github.com/cc14514/go-libp2p-example
# 安裝依賴包
gx install

因爲之前 ipfs 和 libp2p 都已經加載過依賴,所以再次執行 gx install 時資源是在 ipfs 本地庫中備份過的,很快就可以完成

......
[done] [install] 0s    QmW7Ump7YyBMr712Ta3iEVh3ZYcfVvJaPryfbCnyE826b4 go-libp2p-interface-pnet
[done] [install] 0s    QmY9JXR3FupnYAYJWK9aMr9bCpqWKcToQ1tz8DVGTrHpHw go-stream-muxer
[done] [install] 0s    QmP7znopdZogwxPJyRKEZSNnP7HfnUCaQjaMNDmPw8VE2Y go-libp2p-transport-upgrader
[done] [install] 0s    QmdxUuburamoF6zF9qjeQC4WYcWGbWuRmdLacMEsW8ioD8 gogo-protobuf
[done] [install] 44ms  QmUDzeFgYrRmHL2hUB6NZmqcBVQtUzETwmFRUc9onfSSHr go-libp2p

這個步驟可以反覆執行,每次編寫不同的 example 時都可以重複一遍,其實gx的官方文檔也是提議每次都使用一個臨時的 gopath 來執行 gx install 的。我們只要保證 $GOPATH/src/gx/ipfs 目錄中的依賴包只服務於一個工程,就不會有導入依賴包時不知道選那個的麻煩。都說 gx 不好用,其實只是不習慣。


go-libp2p-host 接口

// Host is an object participating in a p2p network, which implements protocols or provides services. It handles requests like a Server, and issues requests like a Client. It is called Host because it is both Server and Client (and Peer may be confusing).

type Host interface {

   // ID returns the (local) [peer.ID](http://peer.ID) associated with this Host

   ID() peer.ID

   // Peerstore returns the Host's repository of Peer Addresses and Keys.

   Peerstore() pstore.Peerstore

   // Returns the listen addresses of the Host

   Addrs() []ma.Multiaddr

   // Networks returns the Network interface of the Host

   Network() inet.Network

   // Mux returns the Mux multiplexing incoming streams to protocol handlers

   Mux() *msmux.MultistreamMuxer

   // Connect ensures there is a connection between this host and the peer with given [peer.ID](http://peer.ID). Connect will absorb the addresses in pi into its internal peerstore. If there is not an active connection, Connect will issue a h.Network.Dial, and block until a connection is open, or an error is returned. 

   // TODO: Relay + NAT.

   Connect(ctx context.Context, pi pstore.PeerInfo) error

   // SetStreamHandler sets the protocol handler on the Host's Mux.

   // This is equivalent to:

   //   host.Mux().SetHandler(proto, handler)

   // (Threadsafe)

   SetStreamHandler(pid protocol.ID, handler inet.StreamHandler)

   // SetStreamHandlerMatch sets the protocol handler on the Host's Mux

   // using a matching function for protocol selection.

   SetStreamHandlerMatch(protocol.ID, func(string) bool, inet.StreamHandler)

   // RemoveStreamHandler removes a handler on the mux that was set by

   // SetStreamHandler

   RemoveStreamHandler(pid protocol.ID)

   // NewStream opens a new stream to given peer p, and writes a p2p/protocol

   // header with given [protocol.ID](http://protocol.ID). If there is no connection to p, attempts

   // to create one. If ProtocolID is "", writes no header.

   // (Threadsafe)

   NewStream(ctx context.Context, p peer.ID, pids ...protocol.ID) (inet.Stream, error)

   // Close shuts down the host, its Network, and services.

   Close() error

   // ConnManager returns this hosts connection manager

   ConnManager() ifconnmgr.ConnManager

}

go-libp2p-net 中的 Network 接口

// Network is the interface used to connect to the outside world. It dials and listens for connections. it uses a Swarm to pool connnections (see swarm pkg, and peerstream.Swarm). Connections are encrypted with a TLS-like protocol.

type Network interface {

   Dialer

   io.Closer

   // SetStreamHandler sets the handler for new streams opened by the remote side. This operation is threadsafe.

   SetStreamHandler(StreamHandler)

   // SetConnHandler sets the handler for new connections opened by the remote side. This operation is threadsafe.

   SetConnHandler(ConnHandler)

   // NewStream returns a new stream to given peer p. If there is no connection to p, attempts to create one.

   NewStream(context.Context, peer.ID) (Stream, error)

   // Listen tells the network to start listening on given multiaddrs.

   Listen(...ma.Multiaddr) error

   // ListenAddresses returns a list of addresses at which this network listens.

   ListenAddresses() []ma.Multiaddr

   // InterfaceListenAddresses returns a list of addresses at which this network listens. It expands "any interface" addresses (/ip4/0.0.0.0, /ip6/::) to use the known local interfaces.

   InterfaceListenAddresses() ([]ma.Multiaddr, error)

   // Process returns the network's Process

   Process() goprocess.Process

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章