It's a simple peer to peer networking library based on Chord made for IPDS project.
The goal of this project is to create a extraordinarly simple to use yet powerfull peer to peer networking library for Python. It's created for IPDS project but can be used by everyone with ease.
That's why:
- It's plug-and-play,
- It's easy to use,
- It's customizable
P2py is packed with many usefull features that will help you build your p2p app faster:
- TCP Hole Punching
- Chord routing system (So you can connect to other node jus using it's numeric ID)
- Temporary / constant connections
- Sending any type of data (that can be represented in bytes)
- Messages
- Requests (p2py's experimental approach that will save you some time)
- Built-in text message type
- Tools for defining and handling your own message types
Install with pip
using the command:
$ pip install p2py
Node B joins the network of Node A
Node A (172.17.0.2:4444) UUID 0
import p2py, time
node = p2py.Node(4444) #Create a node listening on port 4444
node.start() #Start the node
while True: #Wait forever
time.sleep(1)
#Listening on 172.17.0.2:4444
#(172.11.0.3:4444) -> "Hi!"
Node B (172.11.0.3:4445) UUID 1
import p2py, time
node = p2py.Node(4445) #Create a node listening on port 4444
node.start() #Start the node
node.join_network((('172.17.0.2', 4444))) #Join the network of Node A
while not node.joined(): #Wait until the node joins the network
time.sleep(1)
msg = { #Construct a message
'type' : 'text',
'data' : "Hi!"
}
node.send_to_UUID(UUID=0, msg=msg) #Send a message to Node A
#Listening on 172.11.0.3:4444
#Successfully joined the network! Your UUID is 1
#Sent message to UUID 0
In this example I will define a custom request type thet will add 1 to a requested number and return the result bact to requesting peer.
Node A (172.17.0.2:4444)
import p2py, time
node = p2py.Node(4444) #Create a node listening on port 4444
def handle_add(node, conn, request): #Define a handler
n = request.contents['data'] #Get data from request
n_plus_one = n + 1 #Process the data
resp = { #Construct a response
'type' : 'ADD RESP',
'data' : n_plus_one
}
request.respond(resp) #Respond
node.add_handler("ADD", handle_add) #Assign the handler to "ADD" message type
node.start() #Start the node
while True: #Wait forever
time.sleep(1)
#Listening on 172.17.0.2:4444
Node B (172.11.0.3:4445)
import p2py, time
node = p2py.Node(4445) #Create a node listening on port 4444
node.start() #Start the node
node.join_network((('172.17.0.2', 4444))) #Join the network of Node A
while not node.joined(): #Wait until the node joins the network
time.sleep(1)
msg = { #Construct a request
'type' : 'ADD',
'data' : 17
}
resp = node.request(UUID=0, request_msg=msg) #Send a request to Node A and receive response
result = resp['data']
print(f"17 + 1 = {result}")
#Listening on 172.11.0.3:4444
#Successfully joined the network! Your UUID is 1
#Sent message to UUID 0
#17 + 1 = 18
This is documentation of some high level functions (there is a lot more)
Creates node object. It will listen for connections on specified port [int]. If host [str] was not specified it will run on localhost (127.0.0.1)
Example:
example_node = Node(4444)
Connects node to network of peer which address_list [tuple] was given. Argument address_list is a list of 2-tuples of host (str) and port (int): (HOST [str], PORT [int]).
Example:
example_node.connect_to_network([('127.0.0.1', 1234)])
Creates a temporary connection with UUID [Int] (that means it will be closed after sending the message) and sends message [dict].
Message is a JSON dict it this form:
{ 'type' : MESSAGE_TYPE, 'data' : MESSAGE_DATA }
Example:
message = {
'type' : 'TEXT',
'data' : 'Hello!'
}
example_node.send_to_UUID(7, message)
#sends hello to UUID 7
Creates a temporary connection with address [2-tuple] (that means it will be closed after sending the message) and sends message [dict].
Example:
message = {
'type' : 'TEXT',
'data' : 'Hello!'
}
example_node.send_to_address(('127.0.0.1', 1234), message)
#sends hello to 127.0.0.1:1234
Sends message [dict] to connection[Connection]**.
Example:
message = {
'type' : 'TEXT',
'data' : 'Hello!'
}
conn = get_connection_object() #this is a pseudo function. Don't try it as it doesnt exist
example_node.send_to_connection(conn, message)
#sends hello to conn
Sends request [Request] to UUID[Int]** OR address[2-tuple]** OR connection[Connection]** (at least one has to be specified). It returns dict as a response or NoneType if there is no response.
The requested node receives Request object. You can learn about handling requests in this section
Example:
request = {
'type' : 'ADD',
'data' : 17
}
resp = example_node.request(UUID=7, request_msg=request)
print(f"17 + 1 = {resp['data']}")
Sends message [dict] to all peers in your peer book optionally excluding UUIDs[Int] in exclude [List]
Example:
message = {
'type' : 'TEXT',
'data' : 'Hello peer book!'
}
example_node.send_to_peer_book(message)
#sends "Hello peer book!" to peer book
Closes all connections. It's synonymous to leaving the network
Example:
example_node.close_all_connections()
#left the network
Defines a handler for a given message_type. It takes 2 arguments: message_type and handler function.
Handler function has to take 3 arguments:
- node - a pointer to the local node,
- conn - Connection object from which the request was sent,
- request - Request object sent by requesting node.
Example:
def some_function(node, conn, request):
#do something
node.add_handler("some type", some_function)
node.start()
This variable holds node's UUID. It's set to
None
when node is not connected to any network.
Example:
print(example_node.UUID)
#3
This variable holds info about the number of peers in the network Node is connected to
Example:
print(example_node.network_size)
#77
Creates request object
Sends the response to the requester.
Example:
msg = {
'type' : 'SOME RESP',
'data' : 'some data'
}
request.respond(msg)
Sends the requests to another node specified by either address OR UUID.
The request will be processed by another peer but the response will be sent to the same requester.
Example:
#Oh no! I can't answer the request. Maybe UUID 7 knows the answer
request.bounce(UUID=7)
There are many types of messages that do different things. These are some high-level ones:
message = {
'type' : 'TEXT',
'data' : text_message
}