Nim-libp2p Tutorial: A Peer-to-Peer Chat Example (3)
Hi all, welcome to the last article of the nim-libp2p's tutorial series!
This tutorial is for everyone who is interested in building peer-to-peer chatting applications. No Nim programming experience is needed.
In this part, we will walk you through how to configure and establish a libp2p node. The full code can be found in directchat.nim under our main repository.
Hope you'll find it helpful. Happy learning! ;)
* Note: This tutorial is divided into three parts as below:
Part I
: Set up the main function and use multi-thread for processing IO.Part II
: Dial remote peer and allow customized user input commands.Part III
(now): Configure and establish a libp2p node.
Before you start
Let's try to run this example on the same computer with peers running on two different ports!
1. Open a new terminal window and type the following:
nim c -r --threads:on examples/directchat.nim
2. Press enter to use the default address for our first peer.
3. Open another terminal window and start our second peer.
nim c -r --threads:on examples/directchat.nim
4. Type an address with a new port (55504) to serve our second peer. Input /ip4/127.0.0.1/tcp/55504
and press enter.
5. Copy the address of our first peer from the first terminal and connect to the first peer by entering its address in the second terminal.
6. That's all! The two peers are now connected. Anything you type is one termainl will appear on the other terminal. :)
Start coding!
- First, to help us distinguish from the first code file, rename our second.nim file to final.nim. This step is optional becasue our final code will be the same as in directchat.nim.
- Create a procedure for initializing libp2p node.
proc newChatProto(switch: Switch, transp: StreamTransport): ChatProto =
var chatproto = ChatProto(switch: switch, transp: transp, codec: ChatCodec)
# create handler for incoming connection
proc handle(stream: Connection, proto: string) {.async.} =
if chatproto.connected and not chatproto.conn.closed:
echo "a chat session is already in progress - disconnecting!"
await stream.close()
else:
chatproto.conn = stream
chatproto.connected = true
# assign the new handler
chatproto.handler = handle
return chatproto
In the above code, we instantiate a new ChatProto
instance named chatproto
with the values passed in, create a handler for incoming connections, and assign it to our new ChatProto
instance.
Note that we use var
instead of let
for chatproto
here because we will change the value assigned to chatproto
after its initial declaration.
3. Modify the processInput
procedure to initialize node address by user input as follows:
proc processInput(rfd: AsyncFD) {.async.} =
let transp = fromPipe(rfd)
let seckey = PrivateKey.random(RSA, rng[]).get()
var localAddress = DefaultAddr
while true:
echo &"Type an address to bind to or Enter to use the default {DefaultAddr}"
let a = await transp.readLine()
try:
if a.len > 0:
localAddress = a
break
# uise default
break
except:
echo "invalid address"
localAddress = DefaultAddr
continue
First, we generate a random private key using the RSA encryption scheme and use this key to initialize our new peer.
Then, we assign the default address to localAddress
and replace it if the user enters a valid address (with the multiaddress format). If an invalid address is entered, we set the address back to default and continue listening to user input.
4. Build the switch we are going to use for our libp2p node (add the following to the processInput
procedure).
var switch = SwitchBuilder
.init()
.withRng(rng)
.withPrivateKey(seckey)
.withAddress(MultiAddress.init(localAddress).tryGet())
.build()
let chatProto = newChatProto(switch, transp)
switch.mount(chatProto)
let libp2pFuts = await switch.start()
chatProto.started = true
In the final part, transp
was used when instantiating with newChatProto
to let us process the user input messages using pipes. The libp2pFuts
variable stores a sequence of Futures that were returned after the switch has started.
Don'f forget to set chatProto.started
to true
so that we won't print the Help message specified in the writeAndPrint
procedure before.
5. Print the current peer ID and the listening address afte r the node has started (add the following to the processInput
procedure).
let id = peerInfo.peerId.pretty
echo "PeerID: " & id
echo "listening on: "
for a in peerInfo.addrs:
echo &"{a}/p2p/{id}"
6. Start to read and write messages and commands simultaneously and wait for everything to finish after calling switch.start()
(add the following to the processInput
procedure).
await chatProto.readWriteLoop()
await allFuturesThrowing(libp2pFuts)
7. Now your code should be the same as directchat.nim. Run it and start experimenting with your first libp2p chatting application.
nim c -r --threads:on examples/directchat.nim
Conclusion
Congratulations on finishing all the steps of this peer to peer chat example! Now you have learned how to define and establish a customized libp2p node.
Feel free to leave comments below for feedback and suggestions. Thank you for your time reading. See you next time :)