代碼地址: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,這樣能夠同時發送多個請求。