代码地址:https://github.com/brandon-rhodes/fopnp/tree/m/
本书在kindle显示为乱码。
背景:
https://github.com/brandon-rhodes/fopnp/tree/m/playground
以下每台机器可以用docker容器实现。
RPC
RPC简介
远程过程调用(Remote Procedure Call,RPC)是IPC(inter-process communication)的一种,该协议允许运行程序调用另一地址空间(通常是共享网络的另一计算机)的子程序,就像调用本地过程一样。面向对象中常称为远程方法调用RMI(remote method invocation)或远程方法调用。 多用于分布式计算。调用通常比本地慢、可靠性也差些。
RPC的作用:
- 不改变代码进行水平扩展
- 获取网络上磁盘或主机特定的信息。
最初rpc主要用c语言开发,基于二进制传输,类似python的struct,需要知道定义的头文件,且容易发生错误和crash。随着硬件和网络的加速,逐渐改为可读性更好的格式。
老协议发送的数据可能如下:
0, 0, 0, 1, 64, 36, 0, 0, 0, 0, 0, 0
新协议不需要头文件:
<params>
<param><value><i4>41</i4></value></param>
<param><value><double>10.</double></value></param>
</params>
JSON则更加简化。
[1, 10.0]
RPC协议特殊的地方:
- 弱语义:调用的语义不强,含义由API自己确定。基础数据类型:整型、浮点型、字符串、列表
- 调用方法,但是不对方法进行定义。
- 调用和普通方法或函数类似。
RPC的特点如下:
- 传递的数据类型有限。只支持位置参数,因为需要跨语言。 一般只支持数值和字符串、序列或列表、结构体或联合数组。 操作系统相关的文件,live socket、shared memory通常不能传递。
- 异常处理:一般要定义错误信息,而不是简单地打印堆栈。 因为客户端未必有服务器端的代码,打印出这些堆栈意义不大,反而有可能暴露安全问题。
- 有些内省支持,以查看支持的的调用及参数。python因为是动态类型,内省支持较差。
- 有些有寻址支持
- 有些有认证、访问控制 ,不过大多还是基于HTTP。
XML-RPC
通常不会使用XML-RPC,因为它比较笨重。因为它是首批基于HTTP的RPC协议,python提供了内置支持。
标准参见:http://xmlrpc.scripting.com/spec.html。
数据类型: int ; float ; unicode ; list ; dict with unicode keys; with nonstandard extensions, datetime
and None
库:xmlrpclib , SimpleXMLRPCServer , DocXMLRPCServer
xmlrpc_server.py
#!/usr/bin/env python3
# Foundations of Python Network Programming, Third Edition
# https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter18/xmlrpc_server.py
# XML-RPC server
import operator, math
from xmlrpc.server import SimpleXMLRPCServer
from functools import reduce
def main():
server = SimpleXMLRPCServer(('127.0.0.1', 7001))
server.register_introspection_functions()
server.register_multicall_functions()
server.register_function(addtogether)
server.register_function(quadratic)
server.register_function(remote_repr)
print("Server ready")
server.serve_forever()
def addtogether(*things):
"""Add together everything in the list `things`."""
return reduce(operator.add, things)
def quadratic(a, b, c):
"""Determine `x` values satisfying: `a` * x*x + `b` * x + c == 0"""
b24ac = math.sqrt(b*b - 4.0*a*c)
return list(set([ (-b-b24ac) / 2.0*a,
(-b+b24ac) / 2.0*a ]))
def remote_repr(arg):
"""Return the `repr()` rendering of the supplied `arg`."""
return arg
if __name__ == '__main__':
main()
客户端使用内省:
#!/usr/bin/env python3
# Foundations of Python Network Programming, Third Edition
# https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter18/xmlrpc_introspect.py
# XML-RPC client
import xmlrpc.client
def main():
proxy = xmlrpc.client.ServerProxy('http://127.0.0.1:7001')
print('Here are the functions supported by this server:')
for method_name in proxy.system.listMethods():
if method_name.startswith('system.'):
continue
signatures = proxy.system.methodSignature(method_name)
if isinstance(signatures, list) and signatures:
for signature in signatures:
print('{0}({1})'.format(method_name, signature))
else:
print('{0}(...)'.format(method_name,))
method_help = proxy.system.methodHelp(method_name)
if method_help:
print(' ', method_help)
if __name__ == '__main__':
main()
客户端调用:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Foundations of Python Network Programming, Third Edition
# https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter18/xmlrpc_client.py
# XML-RPC client
import xmlrpc.client
def main():
proxy = xmlrpc.client.ServerProxy('http://127.0.0.1:7001')
print(proxy.addtogether('x', 'ÿ', 'z'))
print(proxy.addtogether(20, 30, 4, 1))
print(proxy.quadratic(2, -4, 0))
print(proxy.quadratic(1, 2, 1))
print(proxy.remote_repr((1, 2.0, 'three')))
print(proxy.remote_repr([1, 2.0, 'three']))
print(proxy.remote_repr({'name': 'Arthur',
'data': {'age': 42, 'sex': 'M'}}))
print(proxy.quadratic(1, 0, 1))
if __name__ == '__main__':
main()
执行结果:
# python3 xmlrpc_client.py
x?z
55
[0.0, 8.0]
[-1.0]
[1, 2.0, 'three']
[1, 2.0, 'three']
{'name': 'Arthur', 'data': {'age': 42, 'sex': 'M'}}
Traceback (most recent call last):
File "xmlrpc_client.py", line 22, in <module>
main()
File "xmlrpc_client.py", line 19, in main
print(proxy.quadratic(1, 0, 1))
File "/opt/python3.5/lib/python3.5/xmlrpc/client.py", line 1091, in __call__
return self.__send(self.__name, args)
File "/opt/python3.5/lib/python3.5/xmlrpc/client.py", line 1431, in __request
verbose=self.__verbose
File "/opt/python3.5/lib/python3.5/xmlrpc/client.py", line 1133, in request
return self.single_request(host, handler, request_body, verbose)
File "/opt/python3.5/lib/python3.5/xmlrpc/client.py", line 1149, in single_request
return self.parse_response(resp)
File "/opt/python3.5/lib/python3.5/xmlrpc/client.py", line 1321, in parse_response
return u.close()
File "/opt/python3.5/lib/python3.5/xmlrpc/client.py", line 654, in close
raise Fault(**self._stack[0])
xmlrpc.client.Fault: <Fault 1: "<class 'ValueError'>:math domain error">
一次传递多个调用
#!/usr/bin/env python3
# Foundations of Python Network Programming, Third Edition
# https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter18/xmlrpc_multicall.py
# XML-RPC client performing a multicall
import xmlrpc.client
def main():
proxy = xmlrpc.client.ServerProxy('http://127.0.0.1:7001')
multicall = xmlrpc.client.MultiCall(proxy)
multicall.addtogether('a', 'b', 'c')
multicall.quadratic(2, -4, 0)
multicall.remote_repr([1, 2.0, 'three'])
for answer in multicall():
print(answer)
if __name__ == '__main__':
main()
可以在服务器端的日志看到只有一条记录。SimpleXMLRPCServer不支持认证和TLS安全机制等。
实际往返的数据,对 quadratic()而言如下:
<?xml version='1.0'?>
<methodCall>
<methodName>quadratic</methodName>
<params>
<param>
<value><int>2</int></value>
</param>
<param>
<value><int>-4</int></value>
</param>
<param>
<value><int>0</int></value>
</param>
</params>
</methodCall>
<?xml version='1.0'?>
<methodResponse>
<params>
<param>
<value><array><data>
<value><double>0.0</double></value>
<value><double>8.0</double></value>
</data></array></value>
</param>
</params>
</methodResponse>
JSON-RPC
标准参见: http://json-rpc.org/wiki/specification
数据类型: int ; float ; unicode ; list ; dict with unicode keys; None
库:many third-party, including jsonrpclib
python标准库不支持jsonrpc。
jsonrpclib是流行的 jsonrpc库之一,在python3,名为jsonrpclib-pelix。
服务器端:jsonrpc_server.py
#!/usr/bin/env python3
# Foundations of Python Network Programming, Third Edition
# https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter18/jsonrpc_server.py
# JSON-RPC server needing "pip install jsonrpclib-pelix"
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
def lengths(*args):
"""Measure the length of each input argument.
Given N arguments, this function returns a list of N smaller
lists of the form [len(arg), arg] that each state the length of
an input argument and also echo back the argument itself.
"""
results = []
for arg in args:
try:
arglen = len(arg)
except TypeError:
arglen = None
results.append((arglen, arg))
return results
def main():
server = SimpleJSONRPCServer(('localhost', 7002))
server.register_function(lengths)
print("Starting server")
server.serve_forever()
if __name__ == '__main__':
main()
客户端:jsonrpc_client.py
#!/usr/bin/env python3
# Foundations of Python Network Programming, Third Edition
# https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter18/jsonrpc_client.py
# JSON-RPC client needing "pip install jsonrpclib-pelix"
from jsonrpclib import Server
def main():
proxy = Server('http://localhost:7002')
print(proxy.lengths((1,2,3), 27, {'Sirius': -1.46, 'Rigel': 0.12}))
if __name__ == '__main__':
main()
wireshark的抓包结果:
{"version": "1.1",
"params": [[1, 2, 3], 27, {"Rigel": 0.12, "Sirius": -1.46}],
"method": "lengths"}
{"result": [[3, [1, 2, 3]], [null, 27],
[2, {"Rigel": 0.12, "Sirius": -1.46}]]
JSON-RPC还可以给每个请求增加id,这样可以同时发送多个请求。