• linux 下 rpc python 实例之使用XML-RPC进行远程文件共享


      这是个不错的练习,使用python开发P2P程序,或许通过这个我们可以自己搞出来一个P2P下载工具,类似于迅雷。XML-RPC是一个远程过程调用(remote procedure call,RPC)的分布式计算协议,通过XML将调用函数封装,并使用HTTP协议作为传送机制[摘自维基百科]

    1.先做一个小小的尝试: 首先进入命令行,输入vim pythonServer.py,然后输入一下代码: 

    from simpleXMLRPCServerr import SimpleXMLRPCServerr
    s = SimpleXMLRPCServer(("",4242))  #默认为本机
    def twice(x):
    return x*x
    
    s.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-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之后再次重启,不会出现端口被占用的情况。

    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-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实现分布式系统调用的那些事 

    每天一小步,人生一大步!Good luck~
  • 相关阅读:
    MySql基础教程(三)——查询训练
    MySql基础教程(二)
    MySql基础教程(一)
    解决Eclipse闪退问题的方法总结
    MySQL图形工具 MySQL GUI Tools的安装使用方法
    MySql5.6版修改用户登录密码
    Windows下MySQL解压版的配置
    js 数组容易弄混的那些方法
    如何使CSS--better(系列二)
    如何使CSS--better(系列一)
  • 原文地址:https://www.cnblogs.com/jkmiao/p/4441158.html
Copyright © 2020-2023  润新知