Python如何实现远程方法调用


Posted in Python onAugust 07, 2020

问题

你想在一个消息传输层如 sockets 、multiprocessing connections 或 ZeroMQ 的基础之上实现一个简单的远程过程调用(RPC)。

解决方案

将函数请求、参数和返回值使用pickle编码后,在不同的解释器直接传送pickle字节字符串,可以很容易的实现RPC。 下面是一个简单的PRC处理器,可以被整合到一个服务器中去:

# rpcserver.py

import pickle
class RPCHandler:
  def __init__(self):
    self._functions = { }

  def register_function(self, func):
    self._functions[func.__name__] = func

  def handle_connection(self, connection):
    try:
      while True:
        # Receive a message
        func_name, args, kwargs = pickle.loads(connection.recv())
        # Run the RPC and send a response
        try:
          r = self._functions[func_name](*args,**kwargs)
          connection.send(pickle.dumps(r))
        except Exception as e:
          connection.send(pickle.dumps(e))
    except EOFError:
       pass

要使用这个处理器,你需要将它加入到一个消息服务器中。你有很多种选择, 但是使用 multiprocessing 库是最简单的。下面是一个RPC服务器例子:

from multiprocessing.connection import Listener
from threading import Thread

def rpc_server(handler, address, authkey):
  sock = Listener(address, authkey=authkey)
  while True:
    client = sock.accept()
    t = Thread(target=handler.handle_connection, args=(client,))
    t.daemon = True
    t.start()

# Some remote functions
def add(x, y):
  return x + y

def sub(x, y):
  return x - y

# Register with a handler
handler = RPCHandler()
handler.register_function(add)
handler.register_function(sub)

# Run the server
rpc_server(handler, ('localhost', 17000), authkey=b'peekaboo')

为了从一个远程客户端访问服务器,你需要创建一个对应的用来传送请求的RPC代理类。例如

import pickle

class RPCProxy:
  def __init__(self, connection):
    self._connection = connection
  def __getattr__(self, name):
    def do_rpc(*args, **kwargs):
      self._connection.send(pickle.dumps((name, args, kwargs)))
      result = pickle.loads(self._connection.recv())
      if isinstance(result, Exception):
        raise result
      return result
    return do_rpc

要使用这个代理类,你需要将其包装到一个服务器的连接上面,例如:

>>> from multiprocessing.connection import Client
>>> c = Client(('localhost', 17000), authkey=b'peekaboo')
>>> proxy = RPCProxy(c)
>>> proxy.add(2, 3)

5
>>> proxy.sub(2, 3)
-1
>>> proxy.sub([1, 2], 4)
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "rpcserver.py", line 37, in do_rpc
  raise result
TypeError: unsupported operand type(s) for -: 'list' and 'int'
>>>

要注意的是很多消息层(比如 multiprocessing )已经使用pickle序列化了数据。 如果是这样的话,对 pickle.dumps() 和 pickle.loads() 的调用要去掉。

讨论

RPCHandler 和 RPCProxy 的基本思路是很比较简单的。 如果一个客户端想要调用一个远程函数,比如 foo(1, 2, z=3) ,代理类创建一个包含了函数名和参数的元组 ('foo', (1, 2), {'z': 3}) 。 这个元组被pickle序列化后通过网络连接发生出去。 这一步在 RPCProxy 的 __getattr__() 方法返回的 do_rpc() 闭包中完成。 服务器接收后通过pickle反序列化消息,查找函数名看看是否已经注册过,然后执行相应的函数。 执行结果(或异常)被pickle序列化后返回发送给客户端。我们的实例需要依赖 multiprocessing 进行通信。 不过,这种方式可以适用于其他任何消息系统。例如,如果你想在ZeroMQ之上实习RPC, 仅仅只需要将连接对象换成合适的ZeroMQ的socket对象即可。

由于底层需要依赖pickle,那么安全问题就需要考虑了 (因为一个聪明的黑客可以创建特定的消息,能够让任意函数通过pickle反序列化后被执行)。 因此你永远不要允许来自不信任或未认证的客户端的RPC。特别是你绝对不要允许来自Internet的任意机器的访问, 这种只能在内部被使用,位于防火墙后面并且不要对外暴露。

作为pickle的替代,你也许可以考虑使用JSON、XML或一些其他的编码格式来序列化消息。 例如,本机实例可以很容易的改写成JSON编码方案。还需要将 pickle.loads() 和 pickle.dumps() 替换成 json.loads() 和 json.dumps() 即可:

# jsonrpcserver.py
import json

class RPCHandler:
  def __init__(self):
    self._functions = { }

  def register_function(self, func):
    self._functions[func.__name__] = func

  def handle_connection(self, connection):
    try:
      while True:
        # Receive a message
        func_name, args, kwargs = json.loads(connection.recv())
        # Run the RPC and send a response
        try:
          r = self._functions[func_name](*args,**kwargs)
          connection.send(json.dumps(r))
        except Exception as e:
          connection.send(json.dumps(str(e)))
    except EOFError:
       pass

# jsonrpcclient.py
import json

class RPCProxy:
  def __init__(self, connection):
    self._connection = connection
  def __getattr__(self, name):
    def do_rpc(*args, **kwargs):
      self._connection.send(json.dumps((name, args, kwargs)))
      result = json.loads(self._connection.recv())
      return result
    return do_rpc

实现RPC的一个比较复杂的问题是如何去处理异常。至少,当方法产生异常时服务器不应该奔溃。 因此,返回给客户端的异常所代表的含义就要好好设计了。 如果你使用pickle,异常对象实例在客户端能被反序列化并抛出。如果你使用其他的协议,那得想想另外的方法了。 不过至少,你应该在响应中返回异常字符串。我们在JSON的例子中就是使用的这种方式。

以上就是Python如何实现远程方法调用的详细内容,更多关于Python远程方法调用的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
Python安装第三方库及常见问题处理方法汇总
Sep 13 Python
python 检查是否为中文字符串的方法
Dec 28 Python
django 中的聚合函数,分组函数,F 查询,Q查询
Jul 25 Python
Django Rest framework频率原理与限制
Jul 26 Python
python如何保证输入键入数字的方法
Aug 23 Python
python不使用for计算两组、多个矩形两两间的iou方式
Jan 18 Python
django-csrf使用和禁用方式
Mar 13 Python
python DES加密与解密及hex输出和bs64格式输出的实现代码
Apr 13 Python
python 解决Fatal error in launcher:错误问题
May 21 Python
关于python3.7安装matplotlib始终无法成功的问题的解决
Jul 28 Python
python 实现单例模式的5种方法
Sep 23 Python
Python如何使用循环结构和分支结构
Apr 13 Python
记一次django内存异常排查及解决方法
Aug 07 #Python
python正则表达式 匹配反斜杠的操作方法
Aug 07 #Python
Pygame框架实现飞机大战
Aug 07 #Python
python爬取网易云音乐热歌榜实例代码
Aug 07 #Python
Python变量格式化输出实现原理解析
Aug 06 #Python
Python实现Canny及Hough算法代码实例解析
Aug 06 #Python
vscode调试django项目的方法
Aug 06 #Python
You might like
PHP获取文件绝对路径的代码(上一级目录)
2011/05/29 PHP
PHP预定义变量9大超全局数组用法详解
2016/04/23 PHP
PHP微信开发用Cache 解决数据缓存
2016/07/11 PHP
Yii 框架使用数据库(databases)的方法示例
2020/05/19 PHP
收集的网上用的ajax之chat.js文件
2007/04/08 Javascript
jQuery 操作XML入门
2008/12/25 Javascript
js下获取div中的数据的原理分析
2010/04/07 Javascript
Jquery公告滚动+AJAX后台得到数据
2011/04/14 Javascript
浅谈Javascript面向对象编程
2011/11/15 Javascript
javascript数组输出的两种方式
2015/01/13 Javascript
js实现数字每三位加逗号的方法
2015/02/05 Javascript
关于Javascript回调函数的一个妙用
2016/08/29 Javascript
mac下的nodejs环境安装的步骤
2017/05/24 NodeJs
在 Angular中 使用 Lodash 的方法
2018/02/11 Javascript
Vue仿支付宝支付功能
2018/05/25 Javascript
详解在Node.js中发起HTTP请求的5种方法
2019/01/10 Javascript
Element DateTimePicker日期时间选择器的使用示例
2020/07/27 Javascript
在vue中使用jsonp进行跨域请求接口操作
2020/10/29 Javascript
[05:46]DOTA2英雄梦之声_第18期_陈
2014/06/20 DOTA
[43:53]OG vs EG 2019国际邀请赛淘汰赛 胜者组 BO3 第三场 8.22
2019/09/05 DOTA
[00:59]DOTA2英雄背景故事——上古巨神
2020/06/28 DOTA
python在多玩图片上下载妹子图的实现代码
2013/08/13 Python
python面向对象_详谈类的继承与方法的重载
2017/06/07 Python
Python实现矩阵加法和乘法的方法分析
2017/12/19 Python
python spyder中读取txt为图片的方法
2018/04/27 Python
解决Pandas to_json()中文乱码,转化为json数组的问题
2018/05/10 Python
如何优雅地改进Django中的模板碎片缓存详解
2018/07/04 Python
Django model select的多种用法详解
2019/07/16 Python
python+selenium select下拉选择框定位处理方法
2019/08/24 Python
Python tensorflow实现mnist手写数字识别示例【非卷积与卷积实现】
2019/12/19 Python
用 Python 制作地球仪的方法
2020/04/24 Python
亚历山大·王官网:Alexander Wang
2017/06/23 全球购物
程序运行正确, 但退出时却"core dump"了,怎么回事
2014/02/19 面试题
个人自我评价和职业目标
2014/01/24 职场文书
学习礼仪心得体会
2014/09/01 职场文书
2014党员四风对照检查材料思想汇报
2014/09/17 职场文书