8. Python脚本学习实战笔记八 使用XML-RPC进行文件共享

时间:2022-07-22 05:34:51

8. Python脚本学习实战笔记八 使用XML-RPC进行文件共享

本篇名言:“也许你太平凡,没有令粉蝶断魂的容颜;也许你太常见,没有昙花一现的娇艳;但你却默默伫立,无怨无悔;装点京华,月月年年。”

XML-RPC是一种用于远程调用过程的协议。可以用普通的套接字编程来实现,可能性能更好,但是XML-RPC非常简单易用,而且让代码大大简化。

1.  需求

P2P文件共享程序是在不同计算机上的程序交换文件。P2P交互内,任何节点peer都可以是链接到其他节点。在这样一个由节点组成的虚拟网络中,是没有*节点的,这样网络会更强壮。

                  P2P系统构建会有很多问题,但是大多数P2P系统都有聪明的方法来组织自己的结构。

                  学习使用满足如下即可:

l  节点介绍自己给其他节点

l  向节点请求文件

l  为避免循环以及长距离请求,需要提供请求历史记录

l  程序必须有连接到节点并且将自己表示为可信任参与者的方法

l  必须提供用户界面

用Python实现其实并不是那么的困难的。

2.  工具及准备

使用的主要模块是xmlrpclibSimpleXMLPRCServer。共享界面可以使用标准库中的cmd模块,以获得一些非常有限的并行效果。此外还有threading模块,urlparse模块

3.  初次实现

先做个试验,启动两个交互式的Python解释器。

第一个输入以下内容:

>>> from SimpleXMLRPCServer importSimpleXMLRPCServer

>>>s=SimpleXMLRPCServer(("",4242))

>>> def twice(x):

...    return x*2

...

>>> s.register_function(twice)

>>> s.serve_forever()

另一个输入如下:

>>> from xmlrpclib importServerProxy

>>>s=ServerProxy('http://localhost:4242')

>>> s.twice(2)

4

此时第一个解释器会输出如下:

127.0.0.1 - - [06/Aug/2015 23:00:05]"POST /RPC2 HTTP/1.1" 200 -

 

程序每个节点需要两方面包括:Node要维护什么信息,必须执行什么操作。

 

代码实现如下:

fromxmlrpclibimport ServerProxy

fromos.pathimport join, isfile

fromSimpleXMLRPCServerimportSimpleXMLRPCServer

fromurlparseimport urlparse

importsys

 

MAX_HISTORY_LENGTH = 6

 

OK = 1

FAIL = 2

EMPTY = ''

 

defgetPort(url):

   'Extracts the port from a URL'

   name = urlparse(url)[1]

   parts = name.split(':')

   return int(parts[-1])

 

 

classNode:

   """

    A node in a peer-to-peer network.

    """

   def__init__(self,url, dirname, secret):

        self.url = url

        self.dirname = dirname

        self.secret = secret

        self.known = set()

 

   defquery(self,query, history=[]):

        """

        Performs a query for a file, possiblyasking other known Nodes for

        help. Returns the file as a string.

        """

        code, data = self._handle(query)

        if code == OK:

            return code, data

        else:

            history = history + [self.url]

            if len(history) >= MAX_HISTORY_LENGTH:

                return FAIL, EMPTY

            returnself._broadcast(query, history)

 

   defhello(self,other):

        """

        Used to introduce the Node to otherNodes.

        """

        self.known.add(other)

        return OK

 

   deffetch(self,query, secret):

        """

        Used to make the Node find a file anddownload it.

        """

        if secret !=self.secret:return FAIL

        code, data = self.query(query)

        if code == OK:

            f = open(join(self.dirname,query),'w')

            f.write(data)

            f.close()

            return OK

        else:

            return FAIL

 

 

   def_start(self):

        """

        Used internally to start the XML-RPCserver.

        """

        s = SimpleXMLRPCServer(("", getPort(self.url)),logRequests=False)

        s.register_instance(self)

        s.serve_forever()

 

   def_handle(self,query):

        """

        Used internally to handle queries.

        """

        dir = self.dirname

        name = join(dir, query)

        ifnot isfile(name):return FAIL, EMPTY

        return OK, open(name).read()

 

   def_broadcast(self,query, history):

        """

        Used internally to broadcast a query toall known Nodes.

        """

        for otherinself.known.copy():

            if otherin history:continue

            try:

                s = ServerProxy(other)

                code, data = s.query(query,history)

                if code == OK:

                    return code, data

            except:

                self.known.remove(other)

        return FAIL, EMPTY

 

defmain():

   url, directory, secret = sys.argv[1:]

   n = Node(url, directory, secret)

   n._start()

 

if __name__ =='__main__': main()

 

创建两个文件夹files1和files2,

在files2文件中放入test.txt文件。。

在第一个节点运行

Python simple_node.py http://localhost:4242 files1 secret1

在第二个节点运行

Python simple_node.py http://localhost:4243 files2 secret2

然后运行如下代码:

>>> from xmlrpclib import *

>>>mypeer=ServerProxy('http://localhost:4242')

>>>code,data=mypeer.query('test.txt')

>>> code

2

Code为2表示获取失败。

然后从节点二获取如下:

>>>otherpeer=ServerProxy('http://localhost:4243')

>>>code,data=otherpeer.query('test.txt')

>>> code

1

>>> data

'ok'

把第一个节点介绍给第二个节点

>>> mypeer.hello('http://localhost:4243')

1

然后获取,就可以从第二个节点上得到test.txt文件。

>>> mypeer.query('test.txt')

[1, 'ok']

把第二个节点的文件下载到第一个节点上

>>>mypeer.fetch('test.txt','secret1')

1

在files1文件夹中查看,存在test.txt文件。

 

4.  重构

重启一个Node的话,会提示已经在使用的错误信息;需要实现人性化界面;返回状态代码不方便,不会检查文件是否在文件目录中。

使用cmd模块的Cmd类实现。

可以继承Cmd来创建命令行界面,然后对所有想处理的命令foo实现do_foo方法。

                  需要每个客户端和自己的节点关联起来,如果在所有节点同步前不能做任何事那么就会非常不友好。可以通过启动独立的线程来解决这个问题。

                  使用异常来替换if语句检查返回的代码。

                  使用os.path模块来实现判断给定的文件是否能在给定的目录中找到。

 

重构分解成两个文件:

Client.py如下:

from xmlrpclib import ServerProxy, Fault

from cmd import Cmd

from random import choice

from string import lowercase

from server import Node, UNHANDLED

from threading import Thread

from time import sleep

import sys

 

HEAD_START = 0.1 # Seconds

SECRET_LENGTH = 100

 

def randomString(length):

    """

    Returns a random string ofletters with the given length.

    """

    chars = []

    letters = lowercase[:26]

    while length > 0:

        length -= 1

       chars.append(choice(letters))

    return ''.join(chars)

 

class Client(Cmd):

    """

    A simple text-based interfaceto the Node class.

    """

 

    prompt = '> '

 

 

    def __init__(self, url,dirname, urlfile):

        """

        Sets the url, dirname,and urlfile, and starts the Node

        Server in a separatethread.

        """

        Cmd.__init__(self)

        self.secret =randomString(SECRET_LENGTH)

        n = Node(url, dirname,self.secret)

        t =Thread(target=n._start)

        t.setDaemon(1)

        t.start()

        # Give the server ahead start:

        sleep(HEAD_START)

        self.server =ServerProxy(url)

        for line inopen(urlfile):

            line = line.strip()

           self.server.hello(line)

 

    def do_fetch(self, arg):

        "Call the fetchmethod of the Server."

        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):

        "Exit theprogram."

        print

        sys.exit()

    def do_hello(self, arg):

        print"hello",arg

    do_EOF = do_exit #End-Of-File is synonymous with 'exit'

 

def main():

    urlfile, directory, url =sys.argv[1:]

    client = Client(url,directory, urlfile)

    client.cmdloop()

 

if __name__ == '__main__': main()

Server.py文件如下:

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):

    """

    An exception thatrepresents an unhandled query.

    """

    def __init__(self,message="Couldn't handle the query"):

        Fault.__init__(self,UNHANDLED, message)

 

 

class AccessDenied(Fault):

    """

    An exception that is raisedif a user tries to access a

    resource for which he orshe is not authorized.

    """

    def __init__(self,message="Access denied"):

        Fault.__init__(self,ACCESS_DENIED, message)

 

def inside(dir, name):

    """

    Checks whether a given filename lies within a given directory.

    """

    dir = abspath(dir)

    name = abspath(name)

    returnname.startswith(join(dir, ''))

 

def getPort(url):

    """

    Extracts the port numberfrom a URL.

    """

    name = urlparse(url)[1]

    parts = name.split(':')

    return int(parts[-1])

 

class Node:

    """

    A node in a peer-to-peernetwork.

    """

    def __init__(self, url,dirname, secret):

        self.url = url

        self.dirname = dirname

        self.secret = secret

        self.known = set()

 

    def query(self, query,history=[]):

        """

        Performs a query for afile, possibly asking other known Nodes for

        help. Returns the fileas a string.

        """

        try:

            return self._handle(query)

        except UnhandledQuery:

            history = history +[self.url]

            if len(history)>= MAX_HISTORY_LENGTH: raise

            returnself._broadcast(query, history)

 

 

    def hello(self, other):

        """

        Used to introduce theNode to other Nodes.

        """

        self.known.add(other)

        return 0

 

    def fetch(self, query,secret):

        """

        Used to make the Nodefind a file and download it.

        """

        if secret != self.secret:raise AccessDenied

        result =self.query(query)

        f =open(join(self.dirname, query), 'w')

        f.write(result)

        f.close()

        return 0

 

    def _start(self):

        """

        Used internally tostart the XML-RPC server.

        """

        s =SimpleXMLRPCServer(("", getPort(self.url)), logRequests=False)

       s.register_instance(self)

        s.serve_forever()

 

    def _handle(self, query):

        """

        Used internally tohandle queries.

        """

        dir = self.dirname

        name = join(dir, query)

        if not isfile(name):raise UnhandledQuery

        if not inside(dir,name): raise AccessDenied

        returnopen(name).read()

 

    def _broadcast(self, query,history):

        """

        Used internally tobroadcast a query to all known Nodes.

        """

        for other inself.known.copy():

            if other inhistory: continue

            try:

                s =ServerProxy(other)

                returns.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()

 

执行如下2个客户端:

python client.py urls.txt files2 http://localhost:4243

python client.py urls.txt files1 http://localhost:4242

 

在urls.txt文件中输入:

http://localhost:4243

E:\>python client.py urls.txt files1http://localhost:4242

> fetch files2.txt

这个files2.txt文件如果再files1文件夹中没找到,会自动到files2文件夹中进行寻找。