-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13 from VictorBjelkholm/example/ping
Add ping example
- Loading branch information
Showing
3 changed files
with
321 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'") | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!") | ||
} | ||
``` |