Skip to content

Commit 5180df8

Browse files
authoredMar 3, 2018
Merge pull request libp2p#284 from upperwal/master
Added example "chat" with README
2 parents 91fec89 + d91fc25 commit 5180df8

File tree

3 files changed

+282
-1
lines changed

3 files changed

+282
-1
lines changed
 

‎examples/README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ Let us know if you find any issue or if you want to contribute and add a new tut
1111
- [Building an http proxy with libp2p](./http-proxy)
1212
- [Protocol Multiplexing with multicodecs](./protocol-multiplexing-with-multicodecs)
1313
- [An echo host](./echo)
14-
- [Multicodecs with protobufs](./multipro)
14+
- [Multicodecs with protobufs](./multipro)
15+
- [P2P chat application](./chat)

‎examples/chat/README.md

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# p2p chat app with libp2p
2+
3+
This program demonstrates a simple p2p chat application. It can work between two peers if
4+
1. Both have private IP address (same network).
5+
2. At least one of them has a public IP address.
6+
7+
Assume if 'A' and 'B' are on different networks host 'A' may or may not have a public IP address but host 'B' has one.
8+
9+
Usage: Run `./chat -sp <SOURCE_PORT>` on host 'B' where <SOURCE_PORT> can be any port number. Now run `./chat -d <MULTIADDR_B>` on node 'A' [`<MULTIADDR_B>` is multiaddress of host 'B' which can be obtained from host 'B' console].
10+
11+
## Build
12+
13+
To build the example, first run `make deps` in the root directory.
14+
15+
```
16+
> make deps
17+
> go build ./examples/chat
18+
```
19+
20+
## Usage
21+
22+
On node 'B'
23+
24+
```
25+
> ./chat -sp 3001
26+
Run ./chat -d /ip4/127.0.0.1/tcp/3001/ipfs/QmdXGaeGiVA745XorV1jr11RHxB9z4fqykm6xCUPX1aTJo
27+
28+
2018/02/27 01:21:32 Got a new stream!
29+
> hi (received messages in green colour)
30+
> hello (sent messages in white colour)
31+
> no
32+
```
33+
34+
On node 'A'. Replace 127.0.0.1 with <PUBLIC_IP> if node 'B' has one.
35+
36+
```
37+
> ./chat -d /ip4/127.0.0.1/tcp/3001/ipfs/QmdXGaeGiVA745XorV1jr11RHxB9z4fqykm6xCUPX1aTJo
38+
Run ./chat -d /ip4/127.0.0.1/tcp/3001/ipfs/QmdXGaeGiVA745XorV1jr11RHxB9z4fqykm6xCUPX1aTJo
39+
40+
This node's multiaddress:
41+
/ip4/0.0.0.0/tcp/0/ipfs/QmWVx9NwsgaVWMRHNCpesq1WQAw2T3JurjGDNeVNWifPS7
42+
> hi
43+
> hello
44+
```
45+
46+
**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.**
47+
48+
## Authors
49+
1. Abhishek Upperwal

‎examples/chat/chat.go

+231
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/*
2+
*
3+
* The MIT License (MIT)
4+
*
5+
* Copyright (c) 2014 Juan Batiz-Benet
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*
25+
* This program demonstrate a simple chat application using p2p communication.
26+
*
27+
*/
28+
29+
package main
30+
31+
import (
32+
"bufio"
33+
"context"
34+
"crypto/rand"
35+
"flag"
36+
"fmt"
37+
"io"
38+
"log"
39+
mrand "math/rand"
40+
"os"
41+
42+
"github.com/libp2p/go-libp2p-crypto"
43+
"github.com/libp2p/go-libp2p-host"
44+
"github.com/libp2p/go-libp2p-net"
45+
"github.com/libp2p/go-libp2p-peer"
46+
"github.com/libp2p/go-libp2p-peerstore"
47+
"github.com/libp2p/go-libp2p-swarm"
48+
"github.com/libp2p/go-libp2p/p2p/host/basic"
49+
"github.com/multiformats/go-multiaddr"
50+
)
51+
52+
/*
53+
* addAddrToPeerstore parses a peer multiaddress and adds
54+
* it to the given host's peerstore, so it knows how to
55+
* contact it. It returns the peer ID of the remote peer.
56+
* @credit examples/http-proxy/proxy.go
57+
*/
58+
func addAddrToPeerstore(h host.Host, addr string) peer.ID {
59+
// The following code extracts target's the peer ID from the
60+
// given multiaddress
61+
ipfsaddr, err := multiaddr.NewMultiaddr(addr)
62+
if err != nil {
63+
log.Fatalln(err)
64+
}
65+
pid, err := ipfsaddr.ValueForProtocol(multiaddr.P_IPFS)
66+
if err != nil {
67+
log.Fatalln(err)
68+
}
69+
70+
peerid, err := peer.IDB58Decode(pid)
71+
if err != nil {
72+
log.Fatalln(err)
73+
}
74+
75+
// Decapsulate the /ipfs/<peerID> part from the target
76+
// /ip4/<a.b.c.d>/ipfs/<peer> becomes /ip4/<a.b.c.d>
77+
targetPeerAddr, _ := multiaddr.NewMultiaddr(
78+
fmt.Sprintf("/ipfs/%s", peer.IDB58Encode(peerid)))
79+
targetAddr := ipfsaddr.Decapsulate(targetPeerAddr)
80+
81+
// We have a peer ID and a targetAddr so we add
82+
// it to the peerstore so LibP2P knows how to contact it
83+
h.Peerstore().AddAddr(peerid, targetAddr, peerstore.PermanentAddrTTL)
84+
return peerid
85+
}
86+
87+
func handleStream(s net.Stream) {
88+
log.Println("Got a new stream!")
89+
90+
// Create a buffer stream for non blocking read and write.
91+
rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))
92+
93+
go readData(rw)
94+
go writeData(rw)
95+
96+
// stream 's' will stay open until you close it (or the other side closes it).
97+
}
98+
func readData(rw *bufio.ReadWriter) {
99+
for {
100+
str, _ := rw.ReadString('\n')
101+
102+
if str == "" {
103+
return
104+
}
105+
if str != "\n" {
106+
// Green console colour: \x1b[32m
107+
// Reset console colour: \x1b[0m
108+
fmt.Printf("\x1b[32m%s\x1b[0m> ", str)
109+
}
110+
111+
}
112+
}
113+
114+
func writeData(rw *bufio.ReadWriter) {
115+
stdReader := bufio.NewReader(os.Stdin)
116+
117+
for {
118+
fmt.Print("> ")
119+
sendData, err := stdReader.ReadString('\n')
120+
121+
if err != nil {
122+
panic(err)
123+
}
124+
125+
rw.WriteString(fmt.Sprintf("%s\n", sendData))
126+
rw.Flush()
127+
}
128+
129+
}
130+
131+
func main() {
132+
133+
sourcePort := flag.Int("sp", 0, "Source port number")
134+
dest := flag.String("d", "", "Dest MultiAddr String")
135+
help := flag.Bool("help", false, "Display Help")
136+
debug := flag.Bool("debug", true, "Debug generated same node id on every execution.")
137+
138+
flag.Parse()
139+
140+
if *help {
141+
fmt.Printf("This program demonstrates a simple p2p chat application using libp2p\n\n")
142+
fmt.Printf("Usage: Run './chat -sp <SOURCE_PORT>' where <SOURCE_PORT> can be any port number. Now run './chat -d <MULTIADDR>' where <MULTIADDR> is multiaddress of previous listener host.\n")
143+
144+
os.Exit(0)
145+
}
146+
147+
// If debug is enabled used constant random source else cryptographic randomness.
148+
var r io.Reader
149+
if *debug {
150+
// Constant random source. This will always generate the same host ID on multiple execution.
151+
// Don't do this in production code.
152+
r = mrand.New(mrand.NewSource(int64(*sourcePort)))
153+
} else {
154+
r = rand.Reader
155+
}
156+
157+
// Creates a new RSA key pair for this host
158+
prvKey, pubKey, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r)
159+
160+
if err != nil {
161+
panic(err)
162+
}
163+
164+
// Getting host ID from public key.
165+
// host ID is the hash of public key
166+
nodeID, _ := peer.IDFromPublicKey(pubKey)
167+
168+
// 0.0.0.0 will listen on any interface device
169+
sourceMultiAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", *sourcePort))
170+
171+
// Adding self to the peerstore.
172+
ps := peerstore.NewPeerstore()
173+
ps.AddPrivKey(nodeID, prvKey)
174+
ps.AddPubKey(nodeID, pubKey)
175+
176+
// Creating a new Swarm network.
177+
network, err := swarm.NewNetwork(context.Background(), []multiaddr.Multiaddr{sourceMultiAddr}, nodeID, ps, nil)
178+
179+
if err != nil {
180+
panic(err)
181+
}
182+
183+
// NewHost constructs a new *BasicHost and activates it by attaching its
184+
// stream and connection handlers to the given inet.Network (network).
185+
// Other options like NATManager can also be added here.
186+
// See docs: https://godoc.org/github.com/libp2p/go-libp2p/p2p/host/basic#HostOpts
187+
host := basichost.New(network)
188+
189+
if *dest == "" {
190+
// Set a function as stream handler.
191+
// This function is called when a peer initiate a connection and starts a stream with this peer.
192+
// Only applicable on the receiving side.
193+
host.SetStreamHandler("/chat/1.0.0", handleStream)
194+
195+
fmt.Printf("Run './chat -d /ip4/127.0.0.1/tcp/%d/ipfs/%s' on another console.\n You can replace 127.0.0.1 with public IP as well.\n", *sourcePort, host.ID().Pretty())
196+
fmt.Printf("\nWaiting for incoming connection\n\n")
197+
// Hang forever
198+
<-make(chan struct{})
199+
200+
} else {
201+
202+
// Add destination peer multiaddress in the peerstore.
203+
// This will be used during connection and stream creation by libp2p.
204+
peerID := addAddrToPeerstore(host, *dest)
205+
206+
fmt.Println("This node's multiaddress: ")
207+
// IP will be 0.0.0.0 (listen on any interface) and port will be 0 (choose one for me).
208+
// Although this node will not listen for any connection. It will just initiate a connect with
209+
// one of its peer and use that stream to communicate.
210+
fmt.Printf("%s/ipfs/%s\n", sourceMultiAddr, host.ID().Pretty())
211+
212+
// Start a stream with peer with peer Id: 'peerId'.
213+
// Multiaddress of the destination peer is fetched from the peerstore using 'peerId'.
214+
s, err := host.NewStream(context.Background(), peerID, "/chat/1.0.0")
215+
216+
if err != nil {
217+
panic(err)
218+
}
219+
220+
// Create a buffered stream so that read and writes are non blocking.
221+
rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))
222+
223+
// Create a thread to read and write data.
224+
go writeData(rw)
225+
go readData(rw)
226+
227+
// Hang forever.
228+
select {}
229+
230+
}
231+
}

0 commit comments

Comments
 (0)
Please sign in to comment.