Go의 libp2p 사용 시작

이 글은 Go 프로그래밍 언어를libp2p 개발 점대점 응용 프로그램을 사용하는 방법을 간략하게 소개한다.

카탈로그

  • 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

  • 소개하다.
    이 절은 우리가 본문에서 볼 개념을 설명했다.

    libp2p란?
    libp2p docs:

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



    무엇이 점대점 네트워크 응용 프로그램입니까?
    순수한 피어 네트워크 응용 프로그램

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


    피어 네트워크에 연결된 시스템을 일반적으로 클라이언트나 서버가 아닌 노드라고 합니다.

    노드 인코딩

    libp2p 호스트 만들기
    다음 코드는 기본 옵션을 사용하여 새로운libp2p 호스트를 만듭니다.
    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]
    
    호스트가 연결을 감청하기 위해libp2p가 모든 인터페이스에서 IPv4와 IPv6 주소를 자동으로 선택하는 것을 볼 수 있습니다.이렇게 하면 우리의 노드는 현재 다른 노드가 연결된 서버를 충당할 수 있다.
    만약 이 주소 문자열들이 보기에 매우 이상하다면 걱정하지 마세요.우리는 다음 절에서 노드의 주소 찾기를 깊이 연구할 것이다. 왜냐하면 노드를 연결할 수 있는 노드가 필요하기 때문이다.

    노드에 연결(다른 노드에서)
    이전 절의 노드를 연결하기 전에, 노드 주소가libp2p에서 어떻게 작동하는지 봅시다.libp2p 노드에 연결하는 데 필요한 두 가지 개념, 즉 multiaddr과 노드 ID를 연구하겠습니다.

    다중 주소
    libp2p는 서로 다른 네트워크 전송(즉 회선에서 비트를 전송하고 수신하는 기술)에 많은 작업을 했다.이것은 유연한 주소 찾기 방안이 필요하다.
    노드가 실행하는 출력에서 보이는 주소는 multiaddr 인코딩을 사용합니다(spec 참조).multiaddr에서는 여러 프로토콜과 주소 정보를 인코딩할 수 있습니다.
    이전 노드에서 수행한 출력을 분석해 보겠습니다.
    /ip4/127.0.0.1/tcp/45559
    
    multiaddr 문자열에는 두 개의 프로토콜이 인코딩되어 있습니다. /ip4/127.0.0.1은 IPv4 프로토콜을 사용하는 127.0.0.1의 주소를 알려주고, /tcp/45559은 포트 45559에 층을 나누는 TCP 프로토콜을 알려줍니다.

    노드 ID
    libp2p는 /p2p 프로토콜을 정의했고 multiaddr 문자열의 주소 찾기 부분은 우리가 연결할 노드의 ID입니다.즉, 노드의 주소는 다음과 같습니다.
    /ip4/127.0.0.1/tcp/3000/p2p/NODE_ID
    
    이 중 NODE_ID은 노드의 ID입니다.
    노드는 다른 노드(또는 피어)와의 연결을 보호하기 위해 암호화 키 쌍을 생성해야 합니다.
    노드의 ID는 해당 공개 키의 multihash에 불과합니다.
    이러한 방식(서로 다른 노드를 식별하는 것 제외)은 ID가 유일하며 영구화할 수 있으며 다른 노드에 다른 노드가 보낸 공개 키를 검증하는 방법을 제공한다.

    연결 노드
    여기까지 말하면 우리는 두 노드를 연결하는 코드를 계속 작성할 수 있다.
    먼저 호스트의 주소와 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
    
    그래서 이 노드의 p2p 주소 문자열은 (IPv4 주소를 사용합니다):
    /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
    }
    

    데이터 전송 및 수신
    다른 노드에서 데이터를 직접 보내고 수신하려면libp2p 흐름을 사용할 수 있습니다.
    노드가 새 연결 (입구와 출구) 마다 계수기를 시작하고 초당 흐름을 통해 그것을 보냅니다.동시에 노드는 같은 등급에서 보내는 계수기를 계속 읽을 것이다.
    우선, 우리는 함수를 만들어서 데이터를 흐름에 기록합니다.
    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())
        }
    }
    
    그리고 우리는 코드를 수정해서 두 가지 추가 일을 했다.
  • 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와 인터넷 파일 시스템을 포함한다.)
    마지막으로, 나는 네가 이런 내용을 좋아하길 바란다. 이것은 너로 하여금 인터넷 세계를 시작하는 것에 대해 더욱 흥미를 가지게 할 것이다.

    좋은 웹페이지 즐겨찾기