Skip to content

Commit

Permalink
Merge pull request #13 from VictorBjelkholm/example/ping
Browse files Browse the repository at this point in the history
Add ping example
  • Loading branch information
hsanjuan committed Jun 11, 2018
2 parents fb8d260 + 28f5ea2 commit ae156c4
Show file tree
Hide file tree
Showing 3 changed files with 321 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ make deps

Documentation for this module is maintained in [Godoc](https://godoc.org/github.com/hsanjuan/go-libp2p-gorpc)

There are also examples inside the [examples directory](./examples)

## Contribute

PRs accepted.
Expand Down
166 changes: 166 additions & 0 deletions examples/ping/ping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package main

import (
"bytes"
"context"
"crypto/rand"
"flag"
"fmt"
"time"

"log"

gorpc "github.com/hsanjuan/go-libp2p-gorpc"
libp2p "github.com/libp2p/go-libp2p"
host "github.com/libp2p/go-libp2p-host"
peerstore "github.com/libp2p/go-libp2p-peerstore"
protocol "github.com/libp2p/go-libp2p-protocol"
multiaddr "github.com/multiformats/go-multiaddr"
)

type PingArgs struct {
Data []byte
}
type PingReply struct {
Data []byte
}
type PingService struct{}

func (t *PingService) Ping(argType PingArgs, replyType *PingReply) error {
log.Println("Received a Ping call")
replyType.Data = argType.Data
return nil
}

func createPeer(listenAddr string) host.Host {
ctx := context.Background()

// Create a new libp2p host
h, err := libp2p.New(ctx, libp2p.ListenAddrStrings(listenAddr))
if err != nil {
panic(err)
}
return h
}

var protocolID = protocol.ID("/p2p/rpc/ping")

func startServer() {
log.Println("Launching host")
host := createPeer("/ip4/0.0.0.0/tcp/9000")

log.Printf("Hello World, my hosts ID is %s\n", host.ID().Pretty())
for _, addr := range host.Addrs() {
ipfsAddr, err := multiaddr.NewMultiaddr("/ipfs/" + host.ID().Pretty())
if err != nil {
panic(err)
}
peerAddr := addr.Encapsulate(ipfsAddr)
log.Printf("I'm listening on %s\n", peerAddr)
}

rpcHost := gorpc.NewServer(host, protocolID)

svc := PingService{}
err := rpcHost.Register(&svc)
if err != nil {
panic(err)
}

fmt.Println("Done")

for {
time.Sleep(time.Second * 1)
}
}

func startClient(host string, pingCount, randomDataSize int) {
fmt.Println("Launching client")
client := createPeer("/ip4/0.0.0.0/tcp/9001")
fmt.Printf("Hello World, my hosts ID is %s\n", client.ID().Pretty())
ma, err := multiaddr.NewMultiaddr(host)
if err != nil {
panic(err)
}
peerInfo, err := peerstore.InfoFromP2pAddr(ma)
if err != nil {
panic(err)
}
ctx := context.Background()
err = client.Connect(ctx, *peerInfo)
if err != nil {
panic(err)
}
rpcClient := gorpc.NewClient(client, protocolID)
numCalls := 0
durations := []time.Duration{}
betweenPingsSleep := time.Second * 1

for numCalls < pingCount {
var reply PingReply
var args PingArgs

c := randomDataSize
b := make([]byte, c)
_, err := rand.Read(b)
if err != nil {
panic(err)
}

args.Data = b

time.Sleep(betweenPingsSleep)
startTime := time.Now()
err = rpcClient.Call(peerInfo.ID, "PingService", "Ping", args, &reply)
if err != nil {
panic(err)
}
if !bytes.Equal(reply.Data, b) {
panic("Received wrong amount of bytes back!")
}
endTime := time.Now()
diff := endTime.Sub(startTime)
fmt.Printf("%d bytes from %s (%s): seq=%d time=%s\n", c, peerInfo.ID.String(), peerInfo.Addrs[0].String(), numCalls+1, diff)
numCalls += 1
durations = append(durations, diff)
}

totalDuration := int64(0)
for _, dur := range durations {
totalDuration = totalDuration + dur.Nanoseconds()
}
averageDuration := totalDuration / int64(len(durations))
fmt.Printf("Average duration for ping reply: %s\n", time.Duration(averageDuration))

}

func main() {

var mode string
var host string
var count int
var size int
flag.StringVar(&mode, "mode", "", "host or client mode")
flag.StringVar(&host, "host", "", "address of host to connect to")
flag.IntVar(&count, "count", 10, "number of pings to make")
flag.IntVar(&size, "size", 64, "size of random data in ping message")
flag.Parse()

if mode == "" {
log.Fatal("You need to specify '-mode' to be either 'host' or 'client'")
}

if mode == "host" {
startServer()
return
}
if mode == "client" {
if host == "" {
log.Fatal("You need to specify '-host' when running as a client")
}
startClient(host, count, size)
return
}
log.Fatal("Mode '" + mode + "' not recognized. It has to be either 'host' or 'client'")

}
153 changes: 153 additions & 0 deletions examples/ping/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# go-libp2p-gorpc ping example

Quick example how to build a ping service with go-libp2p-gorpc

This example has two parts, the `host` and the `client`. You can switch between
them with the `-mode` flag that accepts either `host` or `client` as value.

## Usage

Have two terminal windows open in the `examples/ping` directory. In the first
one, run:

```
$ go run ping.go -mode host
```

And then copy one of the "I'm listening on" addresses. In this example, we use
the `127.0.0.1` one which ends up being:

```
/ip4/127.0.0.1/tcp/9000/ipfs/QmTwhWUFdY8NvhmLxE9CzPm29zC9bzfoMGAz2SFV5cb26d
```

Now in the second terminal window, run:

```
$ go run ping.go -mode client -host /ip4/127.0.0.1/tcp/9000/ipfs/QmTwhWUFdY8NvhmLxE9CzPm29zC9bzfoMGAz2SFV5cb26d
```

And you should start seeing log messages showing the duration of each ping, and
finally a average of 10 pings.

```
2018/06/10 12:52:44 Launching client
2018/06/10 12:52:44 Hello World, my hosts ID is Qmapkii8GMB2fMUT66yds9surJUdsZHMtygFSFhPnHa14K
64 bytes from <peer.ID UGZS55> (/ip4/127.0.0.1/tcp/9000): seq=1 time=1.404259ms
64 bytes from <peer.ID UGZS55> (/ip4/127.0.0.1/tcp/9000): seq=2 time=1.338412ms
64 bytes from <peer.ID UGZS55> (/ip4/127.0.0.1/tcp/9000): seq=3 time=892.567µs
64 bytes from <peer.ID UGZS55> (/ip4/127.0.0.1/tcp/9000): seq=4 time=505.573µs
64 bytes from <peer.ID UGZS55> (/ip4/127.0.0.1/tcp/9000): seq=5 time=565.036µs
64 bytes from <peer.ID UGZS55> (/ip4/127.0.0.1/tcp/9000): seq=6 time=765.652µs
64 bytes from <peer.ID UGZS55> (/ip4/127.0.0.1/tcp/9000): seq=7 time=1.296701ms
64 bytes from <peer.ID UGZS55> (/ip4/127.0.0.1/tcp/9000): seq=8 time=804.552µs
64 bytes from <peer.ID UGZS55> (/ip4/127.0.0.1/tcp/9000): seq=9 time=733.054µs
64 bytes from <peer.ID UGZS55> (/ip4/127.0.0.1/tcp/9000): seq=10 time=688.807µs
Average duration for ping reply: 899.461µs
```

## Explanation

Here is some of the important code snippets from this example. Keep in mind
that some information here is hard-coded and error-handling is omitted for brevity
and is not a example of production-ready code. To see a more real version of
this code, please check the `ping.go` file inside this directory.

### Host

First we create our libp2p host:

```golang
host, _ := libp2p.New(ctx, libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/9000"))
```

After that, we create our gorpc host that will received calls

```golang
rpcHost := gorpc.NewServer(host, protocol.ID("/p2p/rpc/ping"))
```

Now, we need to have three structs and one method to be able to respond to the
RPC calls from the client. The arguments and the reply only has one argument,
`Data` which is being sent from the client, and replied back in the response.

```golang
type PingArgs struct {
Data []byte
}
type PingReply struct {
Data []byte
}
type PingService struct{}

func (t *PingService) Ping(argType PingArgs, replyType *PingReply) error {
replyType.Data = argType.Data
return nil
}
```

Once we have those defined, we can register our PingService with the RPCHost

```golang
svc := PingService{}
rpcHost.Register(&svc)
```

Now our host is ready to reply to pings from the client.

### Client

Again, let's first create our libp2p peer

```golang
client, _ := libp2p.New(ctx, libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/9001"))
```

Now we need to first connect to the host that we created before.

```golang
host := "/ip4/127.0.0.1/tcp/9000/ipfs/QmUGZS556mhYSSrFGJpxtt33QQuRDjhsYvFNCW1V3A4wjL"
ma, _ := multiaddr.NewMultiaddr(host)
peerInfo, _ := peerstore.InfoFromP2pAddr(ma)
ctx := context.Background()
client.Connect(ctx, *peerInfo)
```

And now we can create our gorpc client with the newly created libp2p client

```golang
rpcClient := gorpc.NewClient(client, protocol.ID("/p2p/rpc/ping"))
```

Then we can start making our rpc call. We start by defining our reply and arguments

```golang
var reply PingReply
var args PingArgs
```

To make sure that we actually make the call correct, we add some random data
in the arguments so we can check it when we get the reply.

```golang
c := 64
b := make([]byte, c)
rand.Read(b)
args.Data = b
```

Now we can finally make the call itself! Keep in mind this is a blocking call,
and it'll fill out `reply` for you.

```golang
rpcClient.Call(peerInfo.ID, "PingService", "Ping", args, &reply)
```

Once the call above has finished, `reply.Data` should now have the same data
as we had before

```golang
if bytes.Equal(reply.Data, b) {
fmt.Println("Got the same bytes back as we sent!")
}
```

0 comments on commit ae156c4

Please sign in to comment.