Merge pull request #152 from libp2p/doc/improveexamples

Improve echo example
This commit is contained in:
Jeromy Johnson 2016-11-10 12:41:02 -08:00 committed by GitHub
commit ecc1c897af
2 changed files with 93 additions and 36 deletions

View File

@ -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).

View File

@ -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)