go 2 . goで始まる


囲碁プログラミング言語を用いたlibp 2 pを用いたピアツーピアアプリケーションの開発を簡単に紹介した.

目次

  • Introduction
  • What is libp2p?
  • What are peer-to-peer network applications?

  • Coding the Node
  • Creating libp2p hosts

  • Connecting to the node (from another node)
  • Multiaddress
  • Node ID
  • Connecting the nodes
  • Sending and Receiving Data
  • Finding Additional Peers
  • Complete Code
  • Conclusion

  • 導入
    このセクションでは、この投稿でのコンセプトを説明します.

    libp 2 pとは
    からlibp2p docs :

    libp2p is a modular system of protocols, specifications and libraries that enable the development of peer-to-peer network applications.



    ピアツーピアネットワークアプリケーションは何か?
    ピュアピアツーピアネットワークアプリケーションは、その1つです

    the machines connected to it act like both as clients and servers, thus sharing their own hardware resources to make the network function.


    クライアントとサーバの代わりに、マシンに接続されたピアツーピアネットワークは、通常「ノード」と呼ばれています.

    ノードのコーディング

    libp 2 pホストの作成
    以下のコードは、デフォルトオプションで新しいlibp 2 pホストを作成します.
    package main
    
    import (
        "context"
        "fmt"
        "os"
        "os/signal"
        "syscall"
    
        "github.com/libp2p/go-libp2p"
    )
    
    func main() {
        ctx := context.Background()
    
        host, err := libp2p.New(ctx)
        if err != nil {
            panic(err)
        }
        defer host.Close()
    
        fmt.Println(host.Addrs())
    
        sigCh := make(chan os.Signal)
        signal.Notify(sigCh, syscall.SIGKILL, syscall.SIGINT)
        <-sigCh
    }
    
    次の出力を得たコードを実行します
    [/ip6/2804:d45:3613:5400:4b34:ed8f:df00:5055/tcp/43937 /ip6/::1/tcp/43937 /ip4/192.168.1.68/tcp/45559 /ip4/127.0.0.1/tcp/45559]
    
    私たちは、libp 2 pが接続のために聞くホストのためにすべてのインターフェースでIPv 4とIPv 6アドレスを自動的に選んだのを見ることができます.これを行うことによって、私たちのノードは、他の接続するためのサーバーとして動作することができます.
    それらのアドレス文字列が奇妙に見えるならば、心配しないでください.ノードをつなぐ必要があるので、次のセクションのノードアドレッシングに深く潜ります.

    ノードへの接続
    前の節からノードに接続する前に、libp 2 pでノードアドレッシングがどのように機能するかを見てみましょう.libp 2 pノードに接続するために必要な2つの概念を調べます.multiaddr とノードID.

    マルチアドレス
    libp 2 pは、異なるネットワークトランスポート(すなわち、ワイヤー上でビットを送受信するために使用される技術)の上で働くのに多くをします.これは柔軟なアドレッシングスキームを必要とする.
    ノード実行の出力で見たアドレスをエンコードしますmultiaddr を参照spec ). multiaddr 彼らのアドレッシング情報とともに互いの上に多くのプロトコルのコード化を許します.
    前のセクションのノード実行の出力を調べましょう.
    /ip4/127.0.0.1/tcp/45559
    
    つのプロトコルがこれでコード化されますmultiaddr 文字列:/ip4/127.0.0.1 を使用して127.0.0.1 IPv 4プロトコルのアドレスと/tcp/45559 ポート45559上のTCPプロトコル( IPのトップ)に私たちに伝えています.

    ノードID
    libp 2 p定義/p2p プロトコルとそのアドレス指定部分multiaddr stringは接続したいノードのIDです.つまり、ノードのアドレスは次のようになります.
    /ip4/127.0.0.1/tcp/3000/p2p/NODE_ID
    
    どこNODE_ID はノードのIDです.
    ノードは他のノード(またはピア)との接続を確保するために暗号化キーペアを生成する必要があります.
    ノードのIDは単にmultihash 公開鍵の.
    そのような方法(別のノードを識別するのに加えて)は、IDSがユニークで、他のノードによって送られた公開鍵を確認するために他のノードが他のノードのために永久にされることができて、方法を提供することができます.

    ノードの接続
    これにより、二つのノードを接続するコードを書くことができます.
    まず、ホストのアドレスとidを表示します.
    fmt.Println("Addresses:", host.Addrs())
    fmt.Println("ID:", host.ID())
    
    ノードを再び開始します.
    Addresses: [/ip4/192.168.1.68/tcp/44511 /ip4/127.0.0.1/tcp/44511 /ip6/2804:d45:3613:5400:4b34:ed8f:df00:5055/tcp/46471 /ip6/::1/tcp/46471]
    ID: Qmdfuscj69bwzza5nyC1RCMRkV1aoYjQq2nvDYqUYG8Zoq
    
    それで、このノードのためのP 2 Pアドレスストリングは( IPv 4アドレスを使用するでしょう)
    /ip4/127.0.0.1/tcp/44511/p2p/Qmdfuscj69bwzza5nyC1RCMRkV1aoYjQq2nvDYqUYG8Zoq
    
    他のノードに接続するためには、引数といくつかの接続ロジックとしてピアアドレスを受け入れるコードを拡張できます.
    package main
    
    import (
        "context"
        "flag"
        "fmt"
        "os"
        "os/signal"
        "syscall"
    
        "github.com/libp2p/go-libp2p"
        "github.com/libp2p/go-libp2p-core/peer"
        "github.com/multiformats/go-multiaddr"
    )
    
    func main() {
        // Add -peer-address flag
        peerAddr := flag.String("peer-address", "", "peer address")
        flag.Parse()
    
        // Create the libp2p host.
        //
        // Note that we are explicitly passing the listen address and restricting it to IPv4 over the
        // loopback interface (127.0.0.1).
        //
        // Setting the TCP port as 0 makes libp2p choose an available port for us.
        // You could, of course, specify one if you like.
        host, err := libp2p.New(context.Background(), libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0"))
        if err != nil {
            panic(err)
        }
        defer host.Close()
    
        // Print this node's addresses and ID
        fmt.Println("Addresses:", host.Addrs())
        fmt.Println("ID:", host.ID())
    
        // If we received a peer address, we should connect to it.
        if *peerAddr != "" {
            // Parse the multiaddr string.
            peerMA, err := multiaddr.NewMultiaddr(*peerAddr)
            if err != nil {
                panic(err)
            }
            peerAddrInfo, err := peer.AddrInfoFromP2pAddr(peerMA)
            if err != nil {
                panic(err)
            }
    
            // Connect to the node at the given address.
            if err := host.Connect(context.Background(), *peerAddrInfo); err != nil {
                panic(err)
            }
            fmt.Println("Connected to", peerAddrInfo.String())
        }
    
        sigCh := make(chan os.Signal)
        signal.Notify(sigCh, syscall.SIGKILL, syscall.SIGINT)
        <-sigCh
    }
    

    データの送受信
    他のピアから直接データを送受信するとき、libp 2 pストリームを使用することができます.
    ノードを新しい接続(インバウンドとアウトバウンド)のカウンタを開始し、毎秒ストリームを通して送信します.同時に、ノードは同じストリームで送られるカウンタを読み続けます.
    まず、ストリームにデータを書き込む関数を作成します.
    func writeCounter(s network.Stream) {
        var counter uint64
    
        for {
            <-time.After(time.Second)
            counter++
    
            err := binary.Write(s, binary.BigEndian, counter)
            if err != nil {
                panic(err)
            }
        }
    }
    
    その後、ストリームからデータを読み込む関数を作成します.
    func readCounter(s network.Stream) {
        for {
            var counter uint64
    
            err := binary.Read(s, binary.BigEndian, &counter)
            if err != nil {
                panic(err)
            }
    
            fmt.Printf("Received %d from %s\n", counter, s.ID())
        }
    }
    
    次に、コードを2つ追加します.
  • ストリームハンドラを設定するSetStreamHandler 関数(ハンドラ関数は、ピアがストリームをオープンするたびに呼び出されます)
  • 新しいストリームを作成するNewStream 接続後の機能
  • ホストインスタンスを作成した後に、ストリームハンドラ関数を次のコードで設定できます.
    // This gets called every time a peer connects 
    // and opens a stream to this node.
    host.SetStreamHandler(protocolID, func(s network.Stream) {
        go writeCounter(s)
        go readCounter(s)
    })
    
    ピアに接続した後に、新しいストリームを開くことができます.
    s, err := host.NewStream(
        context.Background(), 
        peerAddrInfo.ID, 
        protocolID,
    )
    if err != nil {
        panic(err)
    }
    
    go writeCounter(s)
    go readCounter(s)
    

    追加ピアを見つける
    ピアツーピアネットワークは、接続を行うマシンの中央サーバを必要としません.必要なのはネットワークのノードのアドレスです.
    しかし、そのノードがオフラインになったらどうなりますか?我々は接続を失う.
    それが起こるのを防ぐために、我々はネットワークの追加ピアのアドレスを見つけて、覚えていたいです.
    ネットワークの各々のノードは、彼らが知っている同輩のリストを維持します.各ノードは、他の人によって発見されるために彼ら自身のアドレスを知っているピアに発表するでしょう.
    最後のステップとして、ピアディスカバリーを実装しましょう.
    まず、発見サービスがピアを見つけたときに呼ばれるメソッドを定義する新しい型が必要です.
    type discoveryNotifee struct{}
    
    func (n *discoveryNotifee) HandlePeerFound(peerInfo peer.AddrInfo) {
        fmt.Println("found peer", peerInfo.String())
    }
    
    
    HandlePeerFound ピアが発見されるたびに発見サービスによって呼び出されます.
    次に、ディスカバリーサービスのインスタンスを作成します.この例では、ローカルネットワーク内のピアを検索しようとするMDNSプロトコルを使用しています.
    discoveryService, err := discovery.NewMdnsService(
        context.Background(),
        host,
        time.Second,
        discoveryNamespace,
        )
    if err != nil {
        panic(err)
    }
    defer discoveryService.Close()
    
    discoveryService.RegisterNotifee(&discoveryNotifee{})
    
    このコードを追加した後、他のノードに直接接続できるノードを開始し、カウンタ値を送信することができます.また、ノードは定期的にローカルネットワークのピアを捜して、彼らのIDとアドレスを印刷するでしょう.

    コンプリートコード
    これはこのポストで開発した完全な最終コードです.
    package main
    
    import (
        "context"
        "encoding/binary"
        "flag"
        "fmt"
        "os"
        "os/signal"
        "syscall"
        "time"
    
        "github.com/libp2p/go-libp2p"
        "github.com/libp2p/go-libp2p-core/host"
        "github.com/libp2p/go-libp2p-core/network"
        "github.com/libp2p/go-libp2p-core/peer"
        "github.com/libp2p/go-libp2p/p2p/discovery"
        "github.com/multiformats/go-multiaddr"
    )
    
    const protocolID = "/example/1.0.0"
    const discoveryNamespace = "example"
    
    func main() {
        // Add -peer-address flag
        peerAddr := flag.String("peer-address", "", "peer address")
        flag.Parse()
    
        // Create the libp2p host.
        //
        // Note that we are explicitly passing the listen address and restricting it to IPv4 over the
        // loopback interface (127.0.0.1).
        //
        // Setting the TCP port as 0 makes libp2p choose an available port for us.
        // You could, of course, specify one if you like.
        host, err := libp2p.New(context.Background(), libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0"))
        if err != nil {
            panic(err)
        }
        defer host.Close()
    
        // Print this node's addresses and ID
        fmt.Println("Addresses:", host.Addrs())
        fmt.Println("ID:", host.ID())
    
        // Setup a stream handler.
        //
        // This gets called every time a peer connects and opens a stream to this node.
        host.SetStreamHandler(protocolID, func(s network.Stream) {
            go writeCounter(s)
            go readCounter(s)
        })
    
        // Setup peer discovery.
        discoveryService, err := discovery.NewMdnsService(
            context.Background(),
            host,
            time.Second,
            discoveryNamespace,
        )
        if err != nil {
            panic(err)
        }
        defer discoveryService.Close()
    
        discoveryService.RegisterNotifee(&discoveryNotifee{h: host})
    
        // If we received a peer address, we should connect to it.
        if *peerAddr != "" {
            // Parse the multiaddr string.
            peerMA, err := multiaddr.NewMultiaddr(*peerAddr)
            if err != nil {
                panic(err)
            }
            peerAddrInfo, err := peer.AddrInfoFromP2pAddr(peerMA)
            if err != nil {
                panic(err)
            }
    
            // Connect to the node at the given address.
            if err := host.Connect(context.Background(), *peerAddrInfo); err != nil {
                panic(err)
            }
            fmt.Println("Connected to", peerAddrInfo.String())
    
            // Open a stream with the given peer.
            s, err := host.NewStream(context.Background(), peerAddrInfo.ID, protocolID)
            if err != nil {
                panic(err)
            }
    
            // Start the write and read threads.
            go writeCounter(s)
            go readCounter(s)
        }
    
        sigCh := make(chan os.Signal)
        signal.Notify(sigCh, syscall.SIGKILL, syscall.SIGINT)
        <-sigCh
    }
    
    func writeCounter(s network.Stream) {
        var counter uint64
    
        for {
            <-time.After(time.Second)
            counter++
    
            err := binary.Write(s, binary.BigEndian, counter)
            if err != nil {
                panic(err)
            }
        }
    }
    
    func readCounter(s network.Stream) {
        for {
            var counter uint64
    
            err := binary.Read(s, binary.BigEndian, &counter)
            if err != nil {
                panic(err)
            }
    
            fmt.Printf("Received %d from %s\n", counter, s.ID())
        }
    }
    
    type discoveryNotifee struct {
        h host.Host
    }
    
    func (n *discoveryNotifee) HandlePeerFound(peerInfo peer.AddrInfo) {
        fmt.Println("found peer", peerInfo.String())
    }
    
    

    結論
    ピアツーピアネットワーキング(直接接続,データストリーム,ピアディスカバリー)の様々な特徴を示す基本例を開発した.私は、これらがアーキテクチャのこの種を使って造られることができるアプリケーションのタイプの味を与えると信じています(よく知られた例はBitTorrentと惑星間ファイルシステムを含みます).
    そして最後に、私はあなたのコンテンツが好きで、これはあなたにピアツーピアネットワークの世界で始めることに少し興味を取得することを願っています.