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
安裝還是比較容易的,一定要粗略的看一遍說明書再去看代碼和例子,否則很難發現它的美
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個可選的,因爲要導入的依賴很多所以很久也找不到那個正確的組合。
這裏給一個簡單的解決辦法
- 啓動 ipfs
# 啓動ipfs
ipfs daemon
# 檢查peers,沒有 peers 時是不可用的
ipfs swarm peers
- 用 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
}