代码地址:https://github.com/brandon-rhodes/fopnp/tree/m/html
本书在kindle显示为乱码。python
背景:git
https://github.com/brandon-rhodes/fopnp/tree/m/playgroundgithub
如下每台机器能够用docker容器实现。docker
远程过程调用(Remote Procedure Call,RPC)是IPC(inter-process communication)的一种,该协议容许运行程序调用另外一地址空间(一般是共享网络的另外一计算机)的子程序,就像调用本地过程同样。面向对象中常称为远程方法调用RMI(remote method invocation)或远程方法调用。 多用于分布式计算。调用一般比本地慢、可靠性也差些。json
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协议特殊的地方:
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>
标准参见: 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,这样能够同时发送多个请求。