Skip to content
This repository has been archived by the owner on May 12, 2021. It is now read-only.

Updated "chat" example. Now includes peer discovery [autoconnect] #6

Merged
merged 8 commits into from
Sep 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ Let us know if you find any issue or if you want to contribute and add a new tut
- [An echo host](./echo)
- [Multicodecs with protobufs](./multipro)
- [P2P chat application](./chat)
- [P2P chat application w/ rendezvous peer discovery](./chat-with-rendezvous)
129 changes: 129 additions & 0 deletions chat-with-rendezvous/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# p2p chat app with libp2p [support peer discovery]

This program demonstrates a simple p2p chat application. You will learn how to discover a peer in the network (using kad-dht), connect to it and open a chat stream.

## How to build this example?

```
go get github.com/libp2p/go-libp2p-examples/chat-with-rendezvous

go build chat.go
```

## Usage

Use two different terminal windows to run

```
./chat
```
## So how does it work?

1. **Start a p2p host**
```go
ctx := context.Background()

// libp2p.New constructs a new libp2p Host.
// Other options can be added here.
host, err := libp2p.New(ctx)
```
[libp2p.New](https://godoc.org/github.com/libp2p/go-libp2p#New) is the constructor for libp2p node. It creates a host with given configuration. Right now, all the options are default, documented [here](https://godoc.org/github.com/libp2p/go-libp2p#New)

2. **Set a default handler function for incoming connections.**

This function is called on the local peer when a remote peer initiate a connection and starts a stream with the local peer.
```go
// Set a function as stream handler.
host.SetStreamHandler("/chat/1.1.0", handleStream)
```

```handleStream``` is executed for each new stream incoming to the local peer. ```stream``` is used to exchange data between local and remote peer. This example uses non blocking functions for reading and writing from this stream.

```go
func handleStream(stream net.Stream) {

// Create a buffer stream for non blocking read and write.
rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))

go readData(rw)
go writeData(rw)

// 'stream' will stay open until you close it (or the other side closes it).
}
```

3. **Initiate a new DHT Client with ```host``` as local peer.**


```go
dht, err := dht.New(ctx, host)
```

4. **Connect to IPFS bootstrap nodes.**

These nodes are used to find nearby peers using DHT.

```go
for _, addr := range bootstrapPeers {

iaddr, _ := ipfsaddr.ParseString(addr)

peerinfo, _ := peerstore.InfoFromP2pAddr(iaddr.Multiaddr())

if err := host.Connect(ctx, *peerinfo); err != nil {
fmt.Println(err)
} else {
fmt.Println("Connection established with bootstrap node: ", *peerinfo)
}
}
```

5. **Announce your presence using a rendezvous point.**

[dht.Provide](https://godoc.org/github.com/libp2p/go-libp2p-kad-dht#IpfsDHT.Provide) makes this node announce that it can provide a value for the given key. Where a key in this case is ```rendezvousPoint```. Other peers will hit the same key to find other peers.

```go
if err := dht.Provide(tctx, rendezvousPoint, true); err != nil {
panic(err)
}
```

6. **Find peers nearby.**

[dht.FindProviders](https://godoc.org/github.com/libp2p/go-libp2p-kad-dht#IpfsDHT.FindProviders) will return all the peers who have announced their presence before.

```go
peers, err := dht.FindProviders(tctx, rendezvousPoint)
```

**Note:** Although [dht.Provide](https://godoc.org/github.com/libp2p/go-libp2p-kad-dht#IpfsDHT.Provide) and [dht.FindProviders](https://godoc.org/github.com/libp2p/go-libp2p-kad-dht#IpfsDHT.FindProviders) works for a rendezvous peer discovery, this is not the right way of doing it. Libp2p is currently working on an actual rendezvous protocol ([libp2p/specs#56](https://github.com/libp2p/specs/pull/56)) which can be used for bootstrap purposes, real time peer discovery and application specific routing.

7. **Open streams to peers found.**

Finally we open stream to the peers we found.

```go
for _, p := range peers {

if p.ID == host.ID() || len(p.Addrs) == 0 {
// No sense connecting to ourselves or if addrs are not available
continue
}

stream, err := host.NewStream(ctx, p.ID, "/chat/1.1.0")

if err != nil {
fmt.Println(err)
} else {
rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))

go writeData(rw)
go readData(rw)
}

fmt.Println("Connected to: ", p)
}
```

## Authors
1. Abhishek Upperwal
167 changes: 167 additions & 0 deletions chat-with-rendezvous/chat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package main

import (
"bufio"
"context"
"flag"
"fmt"
"log"
"os"
"time"

cid "github.com/ipfs/go-cid"
iaddr "github.com/ipfs/go-ipfs-addr"
libp2p "github.com/libp2p/go-libp2p"
dht "github.com/libp2p/go-libp2p-kad-dht"
inet "github.com/libp2p/go-libp2p-net"
pstore "github.com/libp2p/go-libp2p-peerstore"
mh "github.com/multiformats/go-multihash"
)

// IPFS bootstrap nodes. Used to find other peers in the network.
var bootstrapPeers = []string{
"/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
"/ip4/104.236.179.241/tcp/4001/ipfs/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM",
"/ip4/104.236.76.40/tcp/4001/ipfs/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64",
"/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu",
"/ip4/178.62.158.247/tcp/4001/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd",
}

var rendezvous = "meet me here"

func handleStream(stream inet.Stream) {
log.Println("Got a new stream!")

// Create a buffer stream for non blocking read and write.
rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))

go readData(rw)
go writeData(rw)

// 'stream' will stay open until you close it (or the other side closes it).
}
func readData(rw *bufio.ReadWriter) {
for {
str, _ := rw.ReadString('\n')

if str == "" {
return
}
if str != "\n" {
// Green console colour: \x1b[32m
// Reset console colour: \x1b[0m
fmt.Printf("\x1b[32m%s\x1b[0m> ", str)
}

}
}

func writeData(rw *bufio.ReadWriter) {
stdReader := bufio.NewReader(os.Stdin)

for {
fmt.Print("> ")
sendData, err := stdReader.ReadString('\n')

if err != nil {
panic(err)
}

rw.WriteString(fmt.Sprintf("%s\n", sendData))
rw.Flush()
}
}

func main() {
help := flag.Bool("h", false, "Display Help")
rendezvousString := flag.String("r", rendezvous, "Unique string to identify group of nodes. Share this with your friends to let them connect with you")
flag.Parse()

if *help {
fmt.Printf("This program demonstrates a simple p2p chat application using libp2p\n\n")
fmt.Printf("Usage: Run './chat in two different terminals. Let them connect to the bootstrap nodes, announce themselves and connect to the peers\n")

os.Exit(0)
}

ctx := context.Background()

// libp2p.New constructs a new libp2p Host.
// Other options can be added here.
host, err := libp2p.New(ctx)
if err != nil {
panic(err)
}

// Set a function as stream handler.
// This function is called when a peer initiate a connection and starts a stream with this peer.
host.SetStreamHandler("/chat/1.1.0", handleStream)

kadDht, err := dht.New(ctx, host)
if err != nil {
panic(err)
}

// Let's connect to the bootstrap nodes first. They will tell us about the other nodes in the network.
for _, peerAddr := range bootstrapPeers {
addr, _ := iaddr.ParseString(peerAddr)
peerinfo, _ := pstore.InfoFromP2pAddr(addr.Multiaddr())

if err := host.Connect(ctx, *peerinfo); err != nil {
fmt.Println(err)
} else {
fmt.Println("Connection established with bootstrap node: ", *peerinfo)
}
}

// We use a rendezvous point "meet me here" to announce our location.
// This is like telling your friends to meet you at the Eiffel Tower.
v1b := cid.V1Builder{Codec: cid.Raw, MhType: mh.SHA2_256}
rendezvousPoint, _ := v1b.Sum([]byte(*rendezvousString))

fmt.Println("announcing ourselves...")
tctx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel()
if err := kadDht.Provide(tctx, rendezvousPoint, true); err != nil {
panic(err)
}

// Now, look for others who have announced
// This is like your friend telling you the location to meet you.
// 'FindProviders' will return 'PeerInfo' of all the peers which
// have 'Provide' or announced themselves previously.
fmt.Println("searching for other peers...")
tctx, cancel = context.WithTimeout(ctx, time.Second*10)
defer cancel()
peers, err := kadDht.FindProviders(tctx, rendezvousPoint)
if err != nil {
panic(err)
}
fmt.Printf("Found %d peers!\n", len(peers))

for _, p := range peers {
fmt.Println("Peer: ", p)
}

for _, p := range peers {
if p.ID == host.ID() || len(p.Addrs) == 0 {
// No sense connecting to ourselves or if addrs are not available
continue
}

stream, err := host.NewStream(ctx, p.ID, "/chat/1.1.0")

if err != nil {
fmt.Println(err)
} else {
rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))

go writeData(rw)
go readData(rw)
}

fmt.Println("Connected to: ", p)
}

select {}
}
2 changes: 2 additions & 0 deletions chat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,7 @@ This node's multiaddress:

**NOTE: debug mode is enabled by default, debug mode will always generate same node id (on each node) on every execution. Disable debug using `--debug false` flag while running your executable.**

**Note:** If you are looking for an implementation with peer discovery, [chat-with-rendezvous](../chat-with-rendezvous), supports peer discovery using a rendezvous point.

## Authors
1. Abhishek Upperwal