tinyp2p: 一个 15 行的 P2P 文件分发程序
tinyp2p 是什么?
tinyp2p 是一个 P2P 的文件分发程序,仅用了 15 行的 Python 代码。这个程序的作 者是 Ed Felten,他在 2004 年发布了这个程序。原始的博文 已经被移 除了,但可以在 web存档 中找到原始的博文。下面是这个程序的代码:
# Original tinyp2p:
# tinyp2p.py 1.0 (documentation at http://freedom-to-tinker.com/tinyp2p.html)
import sys, os, SimpleXMLRPCServer, xmlrpclib, re, hmac # (C) 2004, E.W. Felten
ar,pw,res = (sys.argv,lambda u:hmac.new(sys.argv[1],u).hexdigest(),re.search)
pxy,xs = (xmlrpclib.ServerProxy,SimpleXMLRPCServer.SimpleXMLRPCServer)
def ls(p=""):return filter(lambda n:(p=="")or res(p,n),os.listdir(os.getcwd()))
if ar[2]!="client": # license: http://creativecommons.org/licenses/by-nc-sa/2.0
myU,prs,srv = ("http://"+ar[3]+":"+ar[4], ar[5:],lambda x:x.serve_forever())
def pr(x=[]): return ([(y in prs) or prs.append(y) for y in x] or 1) and prs
def c(n): return ((lambda f: (f.read(), f.close()))(file(n)))[0]
f=lambda p,n,a:(p==pw(myU))and(((n==0)and pr(a))or((n==1)and [ls(a)])or c(a))
def aug(u): return ((u==myU) and pr()) or pr(pxy(u).f(pw(u),0,pr([myU])))
pr() and [aug(s) for s in aug(pr()[0])]
(lambda sv:sv.register_function(f,"f") or srv(sv))(xs((ar[3],int(ar[4]))))
for url in pxy(ar[3]).f(pw(ar[3]),0,[]):
for fn in filter(lambda n:not n in ls(), (pxy(url).f(pw(url),1,ar[4]))[0]):
(lambda fi:fi.write(pxy(url).f(pw(url),2,fn)) or fi.close())(file(fn,"wc"))
由于原始的程序代码为了放进邮件签名里,对代码进行极度的压缩,导致代码非常难读。因 此,为了方便别人阅读,网络上有人做了一个对这个程序的注解。
tinyp2p 的用法
这个程序创建一个小的网络,运行这个程序的目录下的所有文件,都可以被能够访问这个网 络的用户,使用这个程序来下载文件。这个程序只是为了演示如何编写一个简单的 P2P 程 序,它没有处理大型的网络,巨大的数据和安全问题。
它使用的通信协议是 HTTP 和 XML-RPC,使用一个密码作为进入的凭证。这个程序的运行方 法如下:
node0$ ls
tinyp2p.py
node0$ cp tinyp2p.py file0
node0$ python tinyp2p.py 123456 server 192.168.1.101 33330
node1$ ls
tinyp2p.py
node1$ cp tinyp2p.py file1
node1$ python tinyp2p.py 123456 server 192.168.1.102 33331 http://192.168.1.101:33330
node2$ ls
tinyp2p.py
node2$ python tinyp2p.py 123456 client http://192.168.1.101:33330 file*
node2$ ls
file0 file1 tinyp2p.py
tinyp2p 的工作原理
当一个节点作为 server 启动时,如何没有指定它要加入的网络,那么它将会作为一个新网 络的第一个节点启动。随后启动的节点,可以指定节点来加入网络。因此,一个节点可以加 入多个网络。
server 节点就是 RPC 服务器,它会设置一个函数给 client 调用。client 通过调用这个 函数,可以交换节点信息,查询文件,下载文件。
server 节点会和每一个它要通信的节点,交换节点信息。假如节点 n0 要加入节点 n1 和 n2。如果节点 n0 先跟节点 n1 通信,那么 n0 将会把它知道的所有节点信息 [n0, n1, n2] 发送给 n1,然后获取 n1 返回的信息。假如 n1 会返回 [n0, n1, n3, n4], 那么 n0 将这个返回的节点信息和自身的节点信息合并,最后它知道的节点信息是 [n0, n1, n2, n3, n4]。接下来,n0 和 n2 通信,将[n0, n1, n2, n3, n4] 发送给 n2, 将 n2 返回的信息和自身的信息合并。
修复当只运行一个 server 节点时,client 运行出错
当只有一个 server 节点时,运行 client 的出错信息如下:
Traceback (most recent call last):
File "tinyp2p.py", line 111, in <module>
for url in pxy(ar[3]).f(pw(ar[3]),0,[]):
File "/usr/lib/python2.7/xmlrpclib.py", line 1233, in __call__
return self.__send(self.__name, args)
File "/usr/lib/python2.7/xmlrpclib.py", line 1587, in __request
verbose=self.__verbose
File "/usr/lib/python2.7/xmlrpclib.py", line 1273, in request
return self.single_request(host, handler, request_body, verbose)
File "/usr/lib/python2.7/xmlrpclib.py", line 1306, in single_request
return self.parse_response(response)
File "/usr/lib/python2.7/xmlrpclib.py", line 1482, in parse_response
return u.close()
File "/usr/lib/python2.7/xmlrpclib.py", line 794, in close
raise Fault(**self._stack[0])
xmlrpclib.Fault: <Fault 1: "<type 'exceptions.TypeError'>:coercing to Unicode: need string or buffer, list found">
这个错误只在只有一个 server 节点运行时出现。出现这个问题的原因是,只有一个 server节点时,它没有网络的节点信息,返回给 client 的节点信息为空。因此,我们需要 在只有一个节点时,也记录下自身的节点信息,然后返回给 client。修改后的代码如下:
# Original tinyp2p:
# tinyp2p.py 1.0 (documentation at http://freedom-to-tinker.com/tinyp2p.html)
import sys, os, SimpleXMLRPCServer, xmlrpclib, re, hmac # (C) 2004, E.W. Felten
ar,pw,res = (sys.argv,lambda u:hmac.new(sys.argv[1],u).hexdigest(),re.search)
pxy,xs = (xmlrpclib.ServerProxy,SimpleXMLRPCServer.SimpleXMLRPCServer)
def ls(p=""):return filter(lambda n:(p=="")or res(p,n),os.listdir(os.getcwd()))
if ar[2]!="client": # license: http://creativecommons.org/licenses/by-nc-sa/2.0
myU,prs,srv = ("http://"+ar[3]+":"+ar[4], ar[5:],lambda x:x.serve_forever())
prs.append(myU) # 这里是新加的代码
def pr(x=[]): return ([(y in prs) or prs.append(y) for y in x] or 1) and prs
def c(n): return ((lambda f: (f.read(), f.close()))(file(n)))[0]
f=lambda p,n,a:(p==pw(myU))and(((n==0)and pr(a))or((n==1)and [ls(a)])or c(a))
def aug(u): return ((u==myU) and pr()) or pr(pxy(u).f(pw(u),0,pr([myU])))
pr() and [aug(s) for s in aug(pr()[0])]
(lambda sv:sv.register_function(f,"f") or srv(sv))(xs((ar[3],int(ar[4]))))
for url in pxy(ar[3]).f(pw(ar[3]),0,[]):
for fn in filter(lambda n:not n in ls(), (pxy(url).f(pw(url),1,ar[4]))[0]):
(lambda fi:fi.write(pxy(url).f(pw(url),2,fn)) or fi.close())(file(fn,"wc"))
后记
这个神奇的 tinyp2p 是一个大神在一年多以前告诉我的,很羞愧的是,在一年多之后我才 开始阅读学习这段代码,并且获益良多。在这里我要感谢这个大神。:-)