Skip to content

Commit 96e2324

Browse files
committedJun 21, 2019
update readme for new usage
more simple!
1 parent c558197 commit 96e2324

13 files changed

+201
-299
lines changed
 

‎README.md

+85-18
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,99 @@ p2p-python
33
I seek a library that can make a simple P2P network.
44
This library enables you create P2P application.
55

6-
## Require
7-
* Python3 (>=3.5)
8-
* Rust **nightly**
6+
## Specification
7+
* Asynchronous IO
8+
* Pure Python code
9+
* TCP and UDP connection
10+
* Automatic network build
11+
* Python**3.6+**
912

1013
## How to install
14+
warning: **Destructive change from 3.0.0**
1115
```commandline
12-
pip3 install --user setuptools-rust
13-
pip3 install --user nem-ed25519-rust
14-
pip3 install --user p2p-python
16+
pip3 install --user p2p-python>=3.0.0
1517
```
1618

17-
## how to use
18-
* command
19-
* [Basic](doc/BASIC.md)
20-
* [Direct cmd](doc/DIRECT_CMD.md)
21-
* [Broadcast cmd](doc/BROADCAST_CMD.md)
22-
* [another cmd](doc/ANOTHER_CMD.md)
23-
24-
## Tutorial
25-
Please read first of all.
26-
[Example](doc/EXAMPLE.md)
27-
[Document -p2p-pythonの内部動作について-](https://ameblo.jp/namuyan/entry-12398575560.html)
19+
## How to use
20+
basic usage with debug tool `aiomonitor`.
21+
install by `pip3 install --user aiomonitor`.
22+
```python
23+
from p2p_python.utils import setup_p2p_params, setup_logger
24+
from p2p_python.server import Peer2Peer, Peer2PeerCmd
25+
import logging
26+
import asyncio
27+
import aiomonitor
28+
import time
29+
30+
loop = asyncio.get_event_loop()
31+
log = logging.getLogger(__name__)
32+
33+
setup_logger(logging.INFO)
34+
35+
# setup Peer2Peer
36+
setup_p2p_params(
37+
network_ver=11111, # (int) identify other network
38+
p2p_port=2000, # (int) P2P listen port
39+
p2p_accept=True, # (bool) switch on TCP server
40+
p2p_udp_accept=True, # (bool) switch on UDP server
41+
)
42+
p2p = Peer2Peer(listen=100) # allow 100 connection
43+
p2p.setup()
44+
45+
# close method example
46+
def close():
47+
p2p.close()
48+
loop.call_later(1.0, loop.stop)
49+
50+
# You can setup DirectDmd method
51+
class DirectCmd(object):
52+
53+
@staticmethod
54+
async def what_is_your_name(user, data):
55+
print("what_is_your_name", user, data)
56+
return {"you return": time.time()}
57+
58+
@staticmethod
59+
async def get_time_now(user, data):
60+
print("get_time_now", user, data)
61+
return {"get time now": time.time()}
62+
63+
# register methods for DirectCmd
64+
p2p.event.setup_events_from_class(DirectCmd)
65+
# throw cmd by `await p2p.send_direct_cmd(DirectCmd.what_is_your_name, 'kelly')`
66+
# or `await p2p.send_direct_cmd('what_is_your_name', 'kelly')`
67+
68+
# You can setup broadcast policy (default disabled)
69+
# WARNING: You must set strict policy or will be broken by users
70+
async def broadcast_check_normal(user, data):
71+
return True
72+
73+
# overwrite method
74+
p2p.broadcast_check = broadcast_check_normal
75+
76+
# setup netcat monitor
77+
local = locals().copy()
78+
local.update({k: v for k, v in globals().items() if not k.startswith('__')})
79+
log.info('local', list(local.keys()))
80+
aiomonitor.start_monitor(loop, port=3000, locals=local)
81+
log.info(f"you can connect by `nc 127.0.0.1 3000`")
82+
83+
# start event loop
84+
# close by `close()` on netcat console
85+
try:
86+
loop.run_forever()
87+
except KeyboardInterrupt:
88+
log.info("closing")
89+
loop.close()
90+
```
2891

92+
## Documents
93+
* [about inner commands](doc/INNER_CMD.md)
94+
* [for debug](doc/FOR_DEBUG.md)
95+
* [how to work p2p-python? (OLD and JP)](https://ameblo.jp/namuyan/entry-12398575560.html)
2996

3097
## Author
3198
[@namuyan_mine](http://twitter.com/namuyan_mine/)
3299

33100
## Licence
34-
MIT
101+
[MIT](LICENSE)

‎doc/ANOTHER_CMD.md

-37
This file was deleted.

‎doc/BASIC.md

-33
This file was deleted.

‎doc/BROADCAST_CMD.md

-24
This file was deleted.

‎doc/COMMANDS.md

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
Another commands
2+
================
3+
Used for network stabilize.
4+
Please import commands by `from p2p_python.server import Peer2PeerCmd`.
5+
6+
commands
7+
----------------
8+
**ping-pong**
9+
```text
10+
# check delay your node to a peer
11+
12+
<<< await p2p.send_command(Peer2PeerCmd.PING_PONG, data=time.time())
13+
14+
>>> (<User Key:12027 open 23m 127.0.0.1:2001 0/0>, {'ping': None, 'pong': 1561109423.2927444})
15+
```
16+
17+
**get-peer-info**
18+
```text
19+
# find a peer's peer list
20+
21+
<<< await p2p.send_command(Peer2PeerCmd.GET_PEER_INFO)
22+
23+
>>> (<User Army:54510 open 26m 127.0.0.1:2002 0/0>, [[['127.0.0.1', 2000], {'name': 'Army:54510', 'client_ver': '3.0.0', 'network_ver': 12345, 'p2p_accept': True, 'p2p_udp_accept': True, 'p2p_port': 2000, 'start_time': 1561107522, 'last_seen': 1561109616}]])
24+
```
25+
26+
**get-nears**
27+
```text
28+
# for : get peer's connection info
29+
30+
<<< await p2p.send_command(Peer2PeerCmd.GET_NEARS)
31+
32+
>>> (<User Table:48590 open 24m 127.0.0.1:2002 0/0>, [[['127.0.0.1', 2000], {'name': 'Army:54510', 'client_ver': '3.0.0', 'network_ver': 12345, 'p2p_accept': True, 'p2p_udp_accept': True, 'p2p_port': 2000, 'start_time': 1561107522, 'last_seen': 1561109473}]])
33+
```
34+
35+
**check-reachable**
36+
```text
37+
# check PORT connection reachable from outside
38+
39+
<<< await p2p.send_command(Peer2PeerCmd.CHECK_REACHABLE, data={'port': 1000})
40+
41+
>>> (<User Key:12027 open 28m 127.0.0.1:2001 0/0>, False)
42+
```
43+
44+
**direct-cmd**
45+
```text
46+
# You can define any command like README.md
47+
# warning: do not forget check data format is can convert json
48+
49+
<<< await p2p.send_direct_cmd(DirectCmd.what_is_your_name, data='kelly')
50+
51+
>>> (<User Table:48590 open 44m 127.0.0.1:2002 0/0>, {"you return": 1561110.0})
52+
```
53+
54+
note
55+
----
56+
I checked by netcat console.
57+

‎doc/DIRECT_CMD.md

-39
This file was deleted.

‎doc/EXAMPLE.md

-102
This file was deleted.

‎doc/FOR_DEBUG.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
for debugging
2+
====
3+
Good option for debuggers.
4+
5+
```python
6+
from p2p_python.config import Debug
7+
8+
Debug.P_PRINT_EXCEPTION = False # print full exception info
9+
Debug.P_SEND_RECEIVE_DETAIL = False # print send/receive msg info
10+
Debug.F_RECODE_TRAFFIC = False # recode traffic recode to file
11+
```
12+
13+
You can switch debug option online.

‎p2p_python/config.py

-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
class V:
44
# path
55
DATA_PATH = None
6-
TMP_PATH = None
76

87
# info
98
CLIENT_VER = None
@@ -21,8 +20,6 @@ class Debug:
2120
P_PRINT_EXCEPTION = False # print exception info
2221
P_SEND_RECEIVE_DETAIL = False # print receive msg info
2322
F_RECODE_TRAFFIC = False # recode traffic to file
24-
F_SEND_CHANNEL_INFO = False # send to details with channel fnc
25-
F_LONG_MSG_INFO = False # long message info
2623

2724

2825
class PeerToPeerError(Exception):

‎p2p_python/core.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ def start(self, s_family=socket.AF_UNSPEC):
9898
f"tcp{len(tcp_servers)}={V.P2P_ACCEPT} udp{len(udp_servers)}={V.P2P_UDP_ACCEPT}")
9999
self.f_running = True
100100

101-
def get_server_header(self):
101+
def get_my_user_header(self):
102+
"""return my UserHeader format dict"""
102103
return {
103104
'name': V.SERVER_NAME,
104105
'client_ver': V.CLIENT_VER,
@@ -107,7 +108,7 @@ def get_server_header(self):
107108
'p2p_udp_accept': V.P2P_UDP_ACCEPT,
108109
'p2p_port': V.P2P_PORT,
109110
'start_time': self.start_time,
110-
'last_seen': int(time())
111+
'last_seen': int(time()),
111112
}
112113

113114
async def create_connection(self, host, port):
@@ -148,7 +149,7 @@ async def create_connection(self, host, port):
148149
raise PeerToPeerError('timeout on first plain msg receive')
149150

150151
# 2. send my header
151-
send = json.dumps(self.get_server_header()).encode()
152+
send = json.dumps(self.get_my_user_header()).encode()
152153
writer.write(send)
153154
await writer.drain()
154155
self.traffic.put_traffic_up(send)
@@ -318,7 +319,7 @@ async def initial_connection_check(self, reader: StreamReader, writer: StreamWri
318319
# 6. encrypt and send AES key and header
319320
send = json.dumps({
320321
'aes-key': new_user.aeskey,
321-
'header': self.get_server_header(),
322+
'header': self.get_my_user_header(),
322323
})
323324
key = generate_shared_key(my_sec, data['public-key'])
324325
encrypted = AESCipher.encrypt(key, send.encode())

‎p2p_python/server.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def __init__(self, listen=15, f_local=False, default_hook=None, object_hook=None
5656
self.peers = PeerData(os.path.join(V.DATA_PATH, 'peer.dat')) # {(host, port): header,..}
5757
# recode traffic if f_debug true
5858
if Debug.F_RECODE_TRAFFIC:
59-
self.core.traffic.recode_dir = V.TMP_PATH
59+
self.core.traffic.recode_dir = V.DATA_PATH
6060
# serializer/deserializer function
6161
self.default_hook = default_hook
6262
self.object_hook = object_hook
@@ -307,6 +307,9 @@ async def try_reconnect(self, user, reason):
307307
async def send_direct_cmd(self, cmd, data, user=None) -> (User, dict):
308308
if len(self.core.user) == 0:
309309
raise PeerToPeerError('not found peers')
310+
if callable(cmd):
311+
cmd = cmd.__name__
312+
assert isinstance(cmd, str)
310313
user = user if user else random.choice(self.core.user)
311314
send_data = {'cmd': cmd, 'data': data}
312315
receive_user, item = await self.send_command(Peer2PeerCmd.DIRECT_CMD, send_data, user)

‎p2p_python/tool/utils.py

+14-14
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,20 @@ class EventIgnition(object):
2121
def __init__(self):
2222
self.event = dict()
2323

24-
def add_event(self, cmd, fnc, post_fnc=None):
24+
def setup_events_from_class(self, klass):
25+
for cmd, fnc in klass.__dict__.items():
26+
if cmd.startswith('_'):
27+
continue
28+
if isinstance(fnc, staticmethod):
29+
fnc = fnc.__func__
30+
self.add_event(cmd, fnc)
31+
32+
def add_event(self, cmd, fnc):
2533
assert fnc.__code__.co_argcount == 2
26-
if post_fnc:
27-
assert post_fnc.__code__.co_argcount == 2
2834
if cmd in self.event:
2935
raise Exception('already registered cmd')
30-
self.event[cmd] = (fnc, post_fnc)
36+
self.event[cmd] = fnc
37+
log.info(f"add DirectCmd event '{cmd}'")
3138

3239
def remove_event(self, cmd):
3340
if cmd in self.event:
@@ -38,18 +45,11 @@ def have_event(self, cmd):
3845

3946
async def ignition(self, user, cmd, data):
4047
if cmd in self.event:
41-
fnc, post_fnc = self.event[cmd]
48+
fnc = self.event[cmd]
4249
if asyncio.iscoroutinefunction(fnc):
43-
result = await fnc(user, data)
50+
return await fnc(user, data)
4451
else:
45-
result = fnc(user, data)
46-
if post_fnc:
47-
if asyncio.iscoroutinefunction(post_fnc):
48-
return await post_fnc(user, result)
49-
else:
50-
return post_fnc(user, result)
51-
else:
52-
return result
52+
return fnc(user, data)
5353
else:
5454
raise KeyError('Not found cmd "{}"'.format(cmd))
5555

‎p2p_python/utils.py

+23-24
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
from p2p_python.config import V, Debug
2+
import logging
13
import socket
2-
import os
34
import random
4-
from tempfile import gettempdir
5-
from p2p_python.config import V, Debug
5+
import os
66

77

88
NAMES = (
@@ -38,30 +38,22 @@ def get_name():
3838
return "{}:{}".format(random.choice(NAMES), random.randint(10000, 99999))
3939

4040

41-
def setup_p2p_params(network_ver, p2p_port, p2p_accept=True, p2p_udp_accept=True, sub_dir=None,
42-
f_debug=False):
41+
def setup_p2p_params(network_ver, p2p_port, p2p_accept=True, p2p_udp_accept=True, sub_dir=None):
4342
""" setup general connection setting """
44-
if f_debug:
45-
Debug.P_PRINT_EXCEPTION = True
46-
Debug.P_SEND_RECEIVE_DETAIL = True
47-
Debug.F_LONG_MSG_INFO = True
4843
# directory params
4944
if V.DATA_PATH is not None:
5045
raise Exception('Already setup params.')
51-
V.DATA_PATH = os.path.join(os.path.expanduser('~'), 'p2p-python')
52-
V.TMP_PATH = os.path.join(gettempdir(), 'p2p-python')
46+
root_data_dir = os.path.join(os.path.expanduser('~'), 'p2p-python')
47+
if not os.path.exists(root_data_dir):
48+
os.makedirs(root_data_dir)
49+
V.DATA_PATH = os.path.join(root_data_dir, str(p2p_port))
5350
if not os.path.exists(V.DATA_PATH):
5451
os.makedirs(V.DATA_PATH)
55-
if not os.path.exists(V.TMP_PATH):
56-
os.makedirs(V.TMP_PATH)
5752
if sub_dir:
5853
V.DATA_PATH = os.path.join(V.DATA_PATH, sub_dir)
59-
V.TMP_PATH = os.path.join(V.TMP_PATH, sub_dir)
60-
if not os.path.exists(V.DATA_PATH):
61-
os.makedirs(V.DATA_PATH)
62-
if not os.path.exists(V.TMP_PATH):
63-
os.makedirs(V.TMP_PATH)
64-
# Network params
54+
if not os.path.exists(V.DATA_PATH):
55+
os.makedirs(V.DATA_PATH)
56+
# network params
6557
V.CLIENT_VER = get_version()
6658
V.SERVER_NAME = get_name()
6759
V.NETWORK_VER = network_ver
@@ -104,10 +96,17 @@ def is_reachable(host, port):
10496
return False
10597

10698

107-
def trim_msg(item, num):
108-
"""limit a message"""
109-
str_item = str(item)
110-
return str_item[:num] + ('...' if len(str_item) > num else '')
99+
def setup_logger(level=logging.DEBUG, format_str='[%(levelname)-6s] [%(threadName)-10s] [%(asctime)-24s] %(message)s'):
100+
"""setup basic logging handler"""
101+
logger = logging.getLogger()
102+
for sh in logger.handlers:
103+
logger.removeHandler(sh)
104+
logger.setLevel(logging.DEBUG)
105+
formatter = logging.Formatter(format_str)
106+
sh = logging.StreamHandler()
107+
sh.setLevel(level)
108+
sh.setFormatter(formatter)
109+
logger.addHandler(sh)
111110

112111

113112
__all__ = [
@@ -117,5 +116,5 @@ def trim_msg(item, num):
117116
"is_reachable",
118117
"setup_tor_connection",
119118
"setup_p2p_params",
120-
"trim_msg",
119+
"setup_logger",
121120
]

0 commit comments

Comments
 (0)
Please sign in to comment.