Message handling
Message handling is how you will transfer and process data for your protocol. For these tasks it’s helpful to know that everything in P2PD is a pipe with the same features. A ‘connection’ is a pipe with a set destination. A ‘server’ is a pipe with no set destination. Pipes support both callbacks and async-await – or mixing them if you choose.
First lets look at callbacks. Callbacks get a bad rep because people find them confusing but they’re very convenient for servers because they will run only when you get a message. It’s then a nice tidy way to write a protocol instead of writing a recv loop yourself.
from p2pd import *
# Put your custom protocol code here.
async def msg_cb(msg, client_tup, pipe):
# E.G. add a ping feature to your protocol.
if b"PING" in msg:
await pipe.send(b"PONG", client_tup)
async def example():
# Start a new P2P node with your protocol.
node = await P2PNode()
node.add_msg_cb(msg_cb)
The pipe parameter refers to a TCP client connection (if the server pipe was TCP) or a single, multiplexed UDP pipe (if the server pipe was UDP.) This also explains why there’s a client_tup parameter: a multiplexed, UDP socket is connectionless and can be reused for many destinations. So by specifying a destination it makes server code compatible with TCP and UDP.
Let’s look at async-await now.
from p2pd import *
strategies = [P2P_DIRECT, P2P_REVERSE, P2P_PUNCH]
async def example():
node = await P2PNode()
pipe = await node.connect("example.peer", strategies=strategies)
await pipe.send(b"Hello, world!")
buf = await pipe.recv()
await node.close()
The async-await socket functions are non-blocking and won’t stall your program dealing with I/O. As expected, once they are done, your surrounding code can continue where it left off. Simulating the control flow of a regular program. The next section cover general networking with P2PD more in depth. It’s recommended to at least read the first section.