標籤:
這是個不錯的練習,使用python開發P2P程式,或許通過這個我們可以自己搞出來一個P2P下載工具,類似於迅雷。XML-RPC是一個遠端程序呼叫(remote procedure call,RPC)的分散式運算協議,通過XML將調用函數封裝,並使用HTTP協議作為傳送機制[摘自維基百科]
1.先做一個小小的嘗試: 首先進入命令列,輸入vim pythonServer.py,然後輸入一下代碼:
from simpleXMLRPCServerr import SimpleXMLRPCServerrs = SimpleXMLRPCServer(("",4242)) #預設為本機def twice(x):return x*xs.register_function(twice) #向伺服器添加功能s.serve_forever() #啟動伺服器
2. 輸入python,開啟shell 互動命令列
from xmlrpclib import ServerProxy s = ServerProxy(‘http://localhost:4242‘)s.twice(6) #通過ServerProxy調用遠端方法,
3. 開始檔案分享權限設定完整代碼:
3.1 伺服器端Server.py
#coding=utf-8from xmlrpclib import ServerProxy,Faultfrom os.path import join, abspath,isfilefrom SimpleXMLRPCServer import SimpleXMLRPCServerfrom urlparse import urlparseimport sysSimpleXMLRPCServer.allow_reuse_address = 1MAX_HISTORY_LENGTH = 6UNHANDLED = 100ACCESS_DENIED = 200class 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 UnhandledQuerydef main(): url, directory, secret = sys.argv[1:] n = Node(url,directory,secret) n._start()if __name__ == ‘__main__‘: main()
首先來看上面的幾個常量設定: SimpleXMLRPCServer.allow_reuse_address表示,其所佔用的連接埠可以重用,即如果你強制關閉node server之後再次重啟,不會出現連接埠被佔用的情況。
MAX_HISTORY_LENGTH = 6 這個是設定最大的節點長度,因為不能讓讓節點無休止的搜尋下去。
UNHANDLED = 100 ACCESS_DENIED = 200 這倆就是返回碼。
然後再來看個node節點的具體流程。 這個段代碼的流程這這樣的,首先,啟動供遠程調用的伺服器,調用的介面就是Node類。在Node類中有三個方法供遠程調用的,一個是hello,一個是fetch還有一個query。hello 這個方法就是添加鄰節點資訊到當前節點中。而fetch則是用來擷取資料的方法,query是節點之間用來互動的。
在fetch方法中,首先判斷密碼是否正確,然後通過調用自己的query方法尋找資料。我們來看query方法,這個方法中,先是調用私人方法_handle本地尋找,如果沒找到,那麼在通過_broadcast介面在所有已知節點中發送廣播,這裡要注意histroy,每次廣播都會傳遞history這個參數,這個參數的作用有二:一是、防止往重複的節點中發送廣播;二是、限制當前所有連結節點的長度。
理解了一個node server的基礎功能之後,再來看對server進行管理的控制類代碼。
3.2 用戶端代碼 Client.py
#coding=utf-8from xmlrpclib import ServerProxy, Faultfrom cmd import Cmdfrom random import choicefrom string import lowercasefrom server import Node,UNHANDLED #引入前面的serverfrom threading import Threadfrom time import sleepimport sysHEAD_START = 0.1SECRET_LENGTH = 100def 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_exitdef 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進行遠程檔案分享權限設定 感謝樓主!
參考:
1 .python zeromq rpc介紹
2. 聊聊Python用rpc實現分布式系統調用的那些事
linux 下 rpc python 執行個體之使用XML-RPC進行遠程檔案分享權限設定