Python练习3-XML-RPC实现简单的P2P文件共享
XML-RPC實現簡單的P2P文件共享
先來個百度百科:
??????XML-RPC的全稱是XML?Remote?Procedure?Call,即XML(標準通用標記語言下的一個子集)遠程過程調用。它是一套允許運行在不同操作系統、不同環境的程序實現基于Internet過程調用的規范和一系列的實現。這種遠程過程調用使用http作為傳輸協議,XML作為傳送信息的編碼格式。Xml-Rpc的定義盡可能的保持了簡單,但同時能夠傳送、處理、返回復雜的數據結構。這個過程也被大家稱為“分布式計算”。
?
學習資料地址:
https://docs.python.org/3/library/xmlrpc.html
https://www.the5fire.com/python-project8-xml-rpc.html
?
下面是測試例子:環境?Python?3.6?[版本小于3.x需改更改部分代碼]
服務端
from xmlrpc.server import SimpleXMLRPCServer s = SimpleXMLRPCServer(("",4242)) def twice(x):return x*2 s.register_function(twice) s.serve_forever()客戶端
import xmlrpc.client s = xmlrpc.client.ServerProxy('http://localhost:4242') print(s.twice(2))執行結果
?
然后步入正題,寫一個文件共享的例子(python?3.6):
例子很簡單,詳情看注釋。
?
Server.py
from xmlrpc.server import SimpleXMLRPCServer from xmlrpc.client import ServerProxy,Fault from os.path import join,abspath ,isfile import sys import urllibSimpleXMLRPCServer.allow_reuse_address = 1 MAX_HISTORY_LENGTH = 6UNHANDLED = 100 ACCESS_ENIED = 200class UnhandledQuery(Fault):def __init__(self ,message="Couldn't handle the query"):Fault.__init__(self ,UNHANDLED ,message)class AccessDenied(Fault):def __init__(self,message="Access denied"):Fault.__init__(self ,ACCESS_ENIED ,message)def inside(dir ,name):dir = abspath(dir)name = abspath(name)return name.startswith(join(dir ,''))def getPort(url):name = urllib.parse.urlparse(url)[1]patrs = name.split(':')return int(patrs[-1]) class Node:def __init__(self ,url ,dirname ,secret):self.url = urlself.dirname = dirnameself.secret = secretself.known = set()def query(self ,query ,history=[]):try:return self._handle(query)except UnhandledQuery:history = history + [self.url]if len(history) >= MAX_HISTORY_LENGTH:raisereturn self._broadcast(query ,history)def hello(self ,other):self.known.add(other)return 0def fetch(self ,query ,secret):if secret != self.secret: raise AccessDeniedresult = self.query(query)f = open(join(self.dirname,query) ,'w')f.write(result)f.close()return 0def _start(self):s = SimpleXMLRPCServer(("" ,getPort(self.url)),logRequests=False)s.register_instance(self)s.serve_forever()def _handle(self ,query):dir = self.dirnamename = join(dir ,query)if not isfile(name) : raise UnhandledQueryif not inside(dir ,name): raise AccessDeniedreturn open(name).read()def _broadcast(self ,query ,history):for other in self.known.copy():if other in history : continuetry:s = ServerProxy(other)return s.query(query ,history)except Fault:self.known.remove(other)#if Fault.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()Client.py
from xmlrpc.client import ServerProxy,Fault from cmd import Cmd from random import choice from string import ascii_lowercase from server import Node,UNHANDLED from threading import Thread from time import sleep import sysHEAD_START = 0.1 # Seconds SECRET_LENFGTH = 100def randomString(length):chars = []letters = ascii_lowercase[:26]while length > 0:length -= 1chars.append(choice(letters))return ''.join(chars)class Client(Cmd):prompt = '> 'def __init__(self ,url ,dirname ,urlfile):Cmd.__init__(self)self.secret = randomString(SECRET_LENFGTH)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:#if f.faultCode != UNHANDLED : raiseprint("Couldn't find the file"+arg)def do_exit(self ,arg):printsys.exit()do_EOF = do_exitdef main():urlfile,directory,url = sys.argv[1:]client = Client(url ,directory ,urlfile)client.cmdloop() if __name__ == '__main__' : main()屢下上面代碼工作流程,主要分為兩部分,service和Client,service主要是維護一些節點信息,Client主要是用于當前用戶發送請求和交互的地方。
入口是從Client開始,主函數進來接受幾個參數:
(1)Urlfile.txt?路徑,這個是一個文件,里面存著一些其他節點的鏈接url和端口,模擬當前節點用戶的”伙伴”,如果把所有節點看成一張有向圖的話,A->B?A->c?A->D?那么A節點里的這個utlfile里存的就是BCD的鏈接信息。當然這里是為了模擬,實際情況可能是在某個位置獲取,或者是在運行過程中不斷積累的。
(2)第二個參數directory路徑,也就是指向一個路徑,當做是當前節點的共享文件夾。
(3)最后一個url參數,是本地service的node節點建立服務的url和端口,比如要開一個本地端口2333的Node節點服務,直接傳遞http://localhost:2333。
?
得到參數之后,是先進入客戶端client類,構造函數里面先隨機生成一個本地密碼,然后在創建一個Node節點,把Node節點的_start作為線程函數創建線程(_start是創建服務器節點并且開啟監聽),然后在創建一個ServerProxy鏈接自己機器上這個Node節點服務,調用服務的Hello函數把urlfile里面的伙伴信息都hello進去(hello是存的鏈接信息表)。至此Client的初始化完成。
????Client初始化之后,就cmdloop了,相關連的韓式Client里面的兩個函數do_fetch和exit。Fetch函數獲取文件。客戶端通過調用這個發送獲取文件的命令。這里阿敏直接是請求本地Node節點的fetch函數,本地節點這個函數接到命令后是先判斷下密碼(其實覺得這個密碼就是擺設),然后開始進入query函數來查詢文件,query函數里面是先嘗試一波看看文件在不在本地,如果不在的話就判斷下是不是達到請求限制了,如果沒到的話就把自己的朋友鏈拿出來,直接調廣播告調用朋友們的query函數(RPC),朋友的query也是這個邏輯,本地有就直接返回結果,沒有繼續調用朋友的朋友。這個地方如果抽象成圖的話就是深度優先搜索遍歷(DFS),當然可以想辦法BFS。這樣最后調查結果會回溯到發起問題的起始節點,也就是最開始客戶端調用的fetch函數,如果找到文件就保存起來,沒找都就輸出相關提示就行了。這個就是這個XMP-PRC(上面代碼)?簡單例子的實現思路。最后是測試路程。
測試步驟如下
? ? 首先需要建立兩個文件夾,A和C,C文件夾里面創建一個文件,B.txt,在A和C所在文件夾中建立urlsA.txt和urlsC.txt文件。里面在urlsA.txt中寫入:http://localhost:4243,然后開啟兩個命令行
?
打開一個cmd窗口A
? ? ?python?client.py?urlsA.txt?A?http://localhost:4242?
? ? ?fetch?B.txt
執行完這兩條命令,應該是看到的文件無法找到
?
再次打開一個窗口B(第一個窗口不關)
????python?client.py?urls?C.txt?C?http://localhost:4243
????fetch?B.txt
執行完兩條命令,沒有任何輸出,說明文件在這個地方是能找到的。
?
????此時再次回頭在第一個打開的窗口中查找B?fetch?B.txt?發現還是沒有找到,講道理此時應該可以的,因為第一個里面有第二個的通訊地址。
????把上面兩個node反過來啟動就可以了,因為在A啟動的時候,鏈接B,但是B不在,A以為B下線了,就把B從列表里移出去了。
反過來執行,會發現A可以成功吧文件下載到A文件夾下。
????上面所有代碼就是個測試例子,有很多細節沒有處理,可以根據實際情況多改改。好好熟悉熟悉XML-RPC以及P2P的基本原理。
?
?
?
?
總結
以上是生活随笔為你收集整理的Python练习3-XML-RPC实现简单的P2P文件共享的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python练习2-基本聊天程序-虚拟茶
- 下一篇: HTTP自定义Header-(SOCKE