linux 下 rpc python 實例之使用XML-RPC進行遠程文件共享

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

參考:

 1 .python zeromq rpc介紹

 2. 聊聊Python用rpc實現分佈式系統調用的那些事 

相關文章
相關標籤/搜索