這是個不錯的練習,使用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進行遠程文件共享 感謝樓主!
參考: