这是个不错的练习,使用python开发P2P程序,或许经过这个咱们能够本身搞出来一个P2P下载工具,相似于迅雷。XML-RPC是一个远程过程调用(remote procedure call,RPC)的分布式计算协议,经过XML将调用函数封装,并使用HTTP协议做为传送机制[摘自维基百科]html
1.先作一个小小的尝试: 首先进入命令行,输入vim pythonServer.py,而后输入一下代码: node
from simpleXMLRPCServerr import SimpleXMLRPCServerr s = SimpleXMLRPCServer(("",4242)) #默认为本机 def twice(x): return x*x s.register_function(twice) #向服务器添加功能 s.serve_forever() #启动服务器
2. 输入python,打开shell 交互命令行python
from xmlrpclib import ServerProxy s = ServerProxy('http://localhost:4242') s.twice(6) #经过ServerProxy调用远程的方法,
3. 开始文件共享完整代码:
3.1 服务器端Server.pyshell
#coding=utf-8 from xmlrpclib import ServerProxy,Fault from os.path import join, abspath,isfile from SimpleXMLRPCServer import SimpleXMLRPCServer from urlparse import urlparse import sys SimpleXMLRPCServer.allow_reuse_address = 1 MAX_HISTORY_LENGTH = 6 UNHANDLED = 100 ACCESS_DENIED = 200 class UnhandledQuery(Fault): ''' that's show can't handle the query exception ''' def __init__(self,message="Couldn't handle the query"): Fault.__init__(self, UNHANDLED, message) class AccessDenied(Fault): ''' when user try to access the forbiden resources raise exception ''' def __init__(self, message="Access denied"): Fault.__init__(self, ACCESS_DENIED, message) def inside(dir,name): ''' check the dir that user defined is contain the filename the user given ''' dir = abspath(dir) name = abspath(name) return name.startswith(join(dir,'')) def getPort(url): ''' get the port num from the url ''' name = urlparse(url)[1] parts = name.split(':') return int(parts[-1]) class Node: def __init__(self, url, dirname, secret): self.url = url self.dirname = dirname self.secret = secret self.known = set() def query(self, query, history = []): try: return self._handle(query) except UnhandledQuery: history = history + [self.url] if len(history) > MAX_HISTORY_LENGTH: raise return self._broadcast(query,history) def hello(self,other): self.known.add(other) return 0 def fetch(self, query, secret): if secret != self.secret: raise result = self.query(query) f = open(join(self.dirname, query),'w') f.write(result) f.close() return 0 def _start(self): s = SimpleXMLRPCServer(("",getPort(self.url)),logRequests=False) s.register_instance(self) s.serve_forever() def _handle(self, query): dir = self.dirname name = join(dir, query) if not isfile(name):raise UnhandledQuery if not inside(dir,name):raise AccessDenied return open(name).read() def _broadcast(self, query, history): for other in self.known.copy(): if other in history: continue try: s = ServerProxy(other) return s.query(query, history) except Fault, f: if f.faultCode == UNHANDLED:pass else: self.known.remove(other) except: self.known.remove(other) raise UnhandledQuery def main(): url, directory, secret = sys.argv[1:] n = Node(url,directory,secret) n._start() if __name__ == '__main__': main()
首先来看上面的几个常量设置: SimpleXMLRPCServer.allow_reuse_address表示,其所占用的端口能够重用,即若是你强制关闭node server以后再次重启,不会出现端口被占用的状况。vim
MAX_HISTORY_LENGTH = 6 这个是设置最大的节点长度,由于不能让让节点无休止的搜索下去。服务器
UNHANDLED = 100 ACCESS_DENIED = 200 这俩就是返回码。app
而后再来看个node节点的具体流程。 这个段代码的流程这这样的,首先,启动供远程调用的服务器,调用的接口就是Node类。在Node类中有三个方法供远程调用的,一个是hello,一个是fetch还有一个query。hello 这个方法就是添加邻节点信息到当前节点中。而fetch则是用来获取数据的方法,query是节点之间用来交互的。dom
在fetch方法中,首先判断密码是否正确,而后经过调用本身的query方法查找数据。咱们来看query方法,这个方法中,先是调用私有方法_handle本地查找,若是没找到,那么在经过_broadcast接口在全部已知节点中发送广播,这里要注意histroy,每次广播都会传递history这个参数,这个参数的做用有二:一是、防止往重复的节点中发送广播;二是、限制当前全部连接节点的长度。分布式
理解了一个node server的基础功能以后,再来看对server进行管理的控制类代码。ide
3.2 客户端代码 Client.py
#coding=utf-8 from xmlrpclib import ServerProxy, Fault from cmd import Cmd from random import choice from string import lowercase from server import Node,UNHANDLED #引入前面的server from threading import Thread from time import sleep import sys HEAD_START = 0.1 SECRET_LENGTH = 100 def randomString(length): chars = [] letters = lowercase[:26] while length > 0: length -= 1 chars.append(choice(letters)) return ''.join(chars) class Client(Cmd): prompt = '> ' def __init__(self, url, dirname, urlfile): Cmd.__init__(self) self.secret = randomString(SECRET_LENGTH) n = Node(url, dirname, self.secret) t = Thread(target = n._start) t.setDaemon(1) t.start() sleep(HEAD_START) self.server = ServerProxy(url) for line in open(urlfile): line = line.strip() self.server.hello(line) def do_fetch(self, arg): try: self.server.fetch(arg,self.secret) except Fault,f: if f.faultCode != UNHANDLED: raise print "Couldn't find the file",arg def do_exit(self,arg): print sys.exit() do_EOR = do_exit def main(): urlfile, directory, url = sys.argv[1:] client = Client(url, directory, urlfile) client.cmdloop() if __name__ == '__main__': main()
来分析一下这段代码,前面的参数就不看了,很好理解,一开始有一个随机生成密码的函数,作什么用的呢?主要是用来防止别人非法调用该控制所控制的node server的。这密码 咱们也不用记,由于咱们有client的合法使用权。呵呵。
这段代码的整体做用就是为你提供一个可视的命令行的界面,经过继承cmd这个类,来解析你输入的命令,好比程序运行以后,出现命令提示符,你输入fetch,那么它会调用到do_fetch这个方法中来,并把参数传递进来。do_fetch这个方法的所用就是调用node server中的fetch方法,获取资源。另外的一个do_exit很好理解,就是接受exit命令退出程序。
在程序初始化的时候,还有一点须要注意,就是它会读取你urlfile参数传递的文件中的数据,这个里面放的是节点的url地址。读取以后程序会把这些地址加到相邻节点中,供之后访问。不过这个程序还有些不完善的地方就是在程序运行时,若是你修改了url配置的文件,他不会读取你新添加的节点url。不过这个修改很简单,把获取url的代码放到do_fetch中就好了。
在运行程序以前还有一些工做要作。 首先须要创建两个文件夹,A和C,C文件夹里面建立一个文件,B.txt,在A和C所在文件夹中创建urlsA.txt和urlsC.txt文件。里面在urlsA.txt中写入:http://localhost:4243,而后开启两个命令行,
第一个输入:
python client.py urlsA.txt A http://localhost:4242
回车,是否是出来提示符了。输入fetch B.txt回车,看到提示Couldn't find the file B.txt。、
而后在第二个命令行中输入
python client.py urlsC.txt C http://localhost:4243
回车。一样输入fetch B.txt回车,是否是没反应。说明该文件存在。接在在第一个命令行中再次输入fetch B.txt看,是否仍是提示没找到文件,若是你对代码根据我上面的建议进行了修改的话,就不会出现错误了,若是没有修改,此时你须要把输入exit退出程序,再次重启,而后在fetch B.txt,而后到A文件夹下查看一下,看是否是把B.txt下载到你的文件夹中了。
PS:上面的程序只能传输文本文件,大文件或者其余格式的文件没法传输,刚才研究了一下,使用xmlrpclib这个库中的Binary函数便可,具体使用访问为: 先引入xmlrpclib,import xmlrpclib 在server类的的_handle方法中最后返回的那句代码return open(name).read() 修改成 return xmlrpclib.Binary(open(name,'rb').read()) 再把fetch方法中的f.write(result)修改成f.write(result.data) 另外这句话前面的那个写文件的方式要改成wb。
【转自】 python项目练习八:使用XML-RPC进行远程文件共享 感谢楼主!
参考: