diff --git a/examples/echo/README.md b/examples/echo/README.md index 4a0ca8b6d8c889cc270a62e9bcf3a09e321059a9..b19176742292e3e8f0d0b75b0e654de5fa340c53 100644 --- a/examples/echo/README.md +++ b/examples/echo/README.md @@ -1,17 +1,22 @@ # Echo client/server with libp2p -This example can be started in either listen mode (server), or dial mode (client). +This is an example that quickly shows how to use the `go-libp2p` stack, +including Host/Basichost, Network/Swarm, Streams, Peerstores and +Multiaddresses. -In listen mode, it will sit and wait for incoming connections on -the `/echo/1.0.0` protocol. Whenever it receives a stream, it will -write whatever message it received, and close the stream. +This example can be started in either listen mode, or dial mode. -In dial mode, it will connect to the target peer on the given address. -It then opens a stream, writes a short message on the same protocol, -and print whatever reply it receives. +In listen mode, it will sit and wait for incoming connections on the +`/echo/1.0.0` protocol. Whenever it receives a stream, it will write the +message "Hello, world!" over the stream and close it. + +In dial mode, the node will start up, connect to the given address, open a +stream to the target peer, and read a message on the protocol `/echo/1.0.0`. ## Build +From `go-libp2p` base folder: + ``` > make deps > go build ./examples/hosts @@ -22,20 +27,68 @@ and print whatever reply it receives. In one terminal: ``` -> ./hosts -l 4737 -2016/11/06 04:37:00 I am /ip4/127.0.0.1/tcp/4737/ipfs/QmXzbaXtBw6mU29WoeYrCtcRLVbT8asWCcEFVuDy4w6pdq -2016/11/06 04:37:00 listening for connections -2016/11/06 04:37:01 got a new stream -2016/11/06 04:37:01 read request: "Hello, world!" +> ./hosts -l 1235 +2016/11/10 10:45:37 I am /ip4/127.0.0.1/tcp/1234/ipfs/QmNtX1cvrm2K6mQmMEaMxAuB4rTexhd87vpYVot4sEZzxc +2016/11/10 10:45:37 listening for connections ``` -In another, copy the address printed by the listener and do: +The listener libp2p host will print its `Multiaddress`, which indicates how it +can be reached (ip4+tcp) and its randomly generated ID (`QmNtX1cv...`) + +Now, launch another node that talks to the listener: ``` -> ./hosts -d /ip4/127.0.0.1/tcp/4737/ipfs/QmXzbaXtBw6mU29WoeYrCtcRLVbT8asWCcEFVuDy4w6pdq -2016/11/06 04:37:01 I am /ip4/127.0.0.1/tcp/0/ipfs/QmeMNYMmkgoyd8M7y925r4yVVDjKtiYtU4rNCyj7wDWzk1 -2016/11/06 04:37:01 connecting to target -2016/11/06 04:37:01 opening stream -2016/11/06 04:37:01 read reply: "Hello, world!" -> +> ./hosts -d /ip4/127.0.0.1/tcp/1234/ipfs/QmNtX1cvrm2K6mQmMEaMxAuB4rTexhd87vpYVot4sEZzxc -l 1236 ``` + + +The new node with send the message `Hello, world!` to the +listener, which will in turn echo it over the stream and close it. The +listener logs the message, and the sender logs the response. + + +## Details + +The `makeBasicHost()` function creates a +[go-libp2p-basichost](https://godoc.org/github.com/libp2p/go-libp2p/p2p/host/basic) +object. `basichost` objects wrap +[go-libp2 swarms](https://godoc.org/github.com/libp2p/go-libp2p-swarm#Swarm) +and should be used preferentially. A +[go-libp2p-swarm Network](https://godoc.org/github.com/libp2p/go-libp2p-swarm#Network) +is a `swarm` which complies to the +[go-libp2p-net Network interface](https://godoc.org/github.com/libp2p/go-libp2p-net#Network) +and takes care of maintaining streams, connections, multiplexing different +protocols on them, handling incoming connections etc. + +In order to create the swarm (and a `basichost`), the example needs: + + * An + [ipfs-procotol ID](https://godoc.org/github.com/libp2p/go-libp2p-peer#ID) + like `QmNtX1cvrm2K6mQmMEaMxAuB4rTexhd87vpYVot4sEZzxc`. The example + autogenerates this on every run. An optional key-pair to secure + communications can be added to it. The example autogenerates them when + using `-secio`. + * A [Multiaddress](https://godoc.org/github.com/multiformats/go-multiaddr), + which indicates how to reach this peer. There can be several of them + (using different protocols or locations for example). Example: + `/ip4/127.0.0.1/tcp/1234`. + * A + [go-libp2p-peerstore](https://godoc.org/github.com/libp2p/go-libp2p-peerstore), + which is used as a address book which matches node IDs to the + multiaddresses through which they can be contacted. This peerstore gets + autopopulated when manually opening a connection (with + [`Connect()`](https://godoc.org/github.com/libp2p/go-libp2p/p2p/host/basic#BasicHost.Connect). Alternatively, + we can manually + [`AddAddr()`](https://godoc.org/github.com/libp2p/go-libp2p-peerstore#AddrManager.AddAddr) + as in the example. + +A `basichost` can now open streams (bi-directional channel between to peers) +using +[NewStream](https://godoc.org/github.com/libp2p/go-libp2p/p2p/host/basic#BasicHost.NewStream) +and use them to send and receive data tagged with a `Protocol.ID` (a +string). The host can also listen for incoming connections for a given +`Protocol` with +[`SetStreamHandle()`](https://godoc.org/github.com/libp2p/go-libp2p/p2p/host/basic#BasicHost.SetStreamHandler). + +The example makes use of all of this to enable communication between a +listener and a sender using protocol `/echo/1.0.0` (which could be any other thing). diff --git a/examples/echo/main.go b/examples/echo/main.go index 55abc801bab44c4411288e35f7d819164ab3a7d3..3fedee8b2fc46ef0277b60373d801aa239b38a37 100644 --- a/examples/echo/main.go +++ b/examples/echo/main.go @@ -8,20 +8,22 @@ import ( "log" "strings" - bhost "github.com/libp2p/go-libp2p/p2p/host/basic" - + golog "github.com/ipfs/go-log" host "github.com/libp2p/go-libp2p-host" inet "github.com/libp2p/go-libp2p-net" net "github.com/libp2p/go-libp2p-net" peer "github.com/libp2p/go-libp2p-peer" + peerstore "github.com/libp2p/go-libp2p-peerstore" pstore "github.com/libp2p/go-libp2p-peerstore" swarm "github.com/libp2p/go-libp2p-swarm" + bhost "github.com/libp2p/go-libp2p/p2p/host/basic" testutil "github.com/libp2p/go-testutil" ma "github.com/multiformats/go-multiaddr" + gologging "github.com/whyrusleeping/go-logging" ) // create a 'Host' with a random peer to listen on the given address -func makeDummyHost(listen string, secio bool) (host.Host, error) { +func makeBasicHost(listen string, secio bool) (host.Host, error) { addr, err := ma.NewMultiaddr(listen) if err != nil { return nil, err @@ -61,30 +63,36 @@ func makeDummyHost(listen string, secio bool) (host.Host, error) { } func main() { + golog.SetAllLoggers(gologging.INFO) // Change to DEBUG for extra info listenF := flag.Int("l", 0, "wait for incoming connections") target := flag.String("d", "", "target peer to dial") secio := flag.Bool("secio", false, "enable secio") flag.Parse() + if *listenF == 0 { + log.Fatal("Please provide a port to bind on with -l") + } + listenaddr := fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", *listenF) - ha, err := makeDummyHost(listenaddr, *secio) + ha, err := makeBasicHost(listenaddr, *secio) if err != nil { log.Fatal(err) } // Set a stream handler on host A ha.SetStreamHandler("/echo/1.0.0", func(s net.Stream) { - log.Println("got a new stream") - doEcho(s) + log.Println("Got a new stream!") defer s.Close() + doEcho(s) }) if *target == "" { log.Println("listening for connections") select {} // hang forever } + // This is where the listener code ends ipfsaddr, err := ma.NewMultiaddr(*target) if err != nil { @@ -102,25 +110,19 @@ func main() { } tptaddr := strings.Split(ipfsaddr.String(), "/ipfs/")[0] + // This creates a MA with the "/ip4/ipaddr/tcp/port" part of the target tptmaddr, err := ma.NewMultiaddr(tptaddr) if err != nil { log.Fatalln(err) } - pi := pstore.PeerInfo{ - ID: peerid, - Addrs: []ma.Multiaddr{tptmaddr}, - } - - log.Println("connecting to target") - err = ha.Connect(context.Background(), pi) - if err != nil { - log.Fatalln(err) - } + // We need to add the target to our peerstore, so we know how we can + // contact it + ha.Peerstore().AddAddr(peerid, tptmaddr, peerstore.PermanentAddrTTL) log.Println("opening stream") // make a new stream from host B to host A - // it should be handled on host A by the handler we set + // it should be handled on host A by the handler we set above s, err := ha.NewStream(context.Background(), peerid, "/echo/1.0.0") if err != nil { log.Fatalln(err) @@ -139,6 +141,8 @@ func main() { log.Printf("read reply: %q\n", out) } +// doEcho reads some data from a stream, writes it back and closes the +// stream. func doEcho(s inet.Stream) { buf := make([]byte, 1024) n, err := s.Read(buf)