本文将会使用Python实现一个最简单的RPC框架,玩具向,不具备实用意义,但可让你清醒的理解RPC框架的几个组成部分,只是比看Python自带的xmlrpc清晰。python
本文须要一点Python socket基础。docker
若是你对Python Socket基础方面的内容不是很熟悉,推荐阅读Real Python的「Socket Programming in Python (Guide)」json
吐槽一下VSCode,在开发一些比较复杂的Python项目时,VSCode的debug功能让人感到蛋疼,询问了Windows下使用VSCode的同事,都没有这样的问题,不清楚VSCode对Mac的支持是否存在问题,还只是我单纯的不会用:(网络
本文代码比较简单,因此仍是使用VSCode进行开发。那咱们开始吧!框架
在实现前,简单理一下总体思路。socket
1.Network Service 直接使用Python Socket相关的API实现
2.传输数据使用JSON,在Socket层会被压成二进制,咱们无需关心ide
模仿xmlrpc,Client与Server都采用Minix多继承机制来实现,每一个类负责自身的事情,最终暴露出现的只有一个类中有限的方法。oop
先从Client端开始实现。优化
# client.py import rpcclient c = rpcclient.RPCClient() c.connect('127.0.0.1', 5000) res = c.add(1, 2, c=3) print(f'res: [{res}]')
实例化rpcclient.RPCClient类,而后调用connect方法连接Server端,随后直接调用Server端的add方法,该方法的效果就是将传入的数据进行累加并将累加的结果返回,最后将add方法返回的结果打印出了。ui
RPCClient类继承于TCPClient类与RPCStub类。
# rpclient.py class RPCClient(TCPClient, RPCStub): pass
其中TCPClient负责经过Socket实现TCP连接并将数据请求过去,而RPCStub类主要将Client端调用Server端方法的相关信息打包,而后调用TCPClient类中的方法发送则可,两个类一样实如今rpclient.py文件中,代码以下。
class TCPClient(object): def __init__(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) def connect(self, host, port): '''连接Server端''' self.sock.connect((host, port)) def send(self, data): '''将数据发送到Server端''' self.sock.send(data) def recv(self, length): '''接受Server端回传的数据''' return self.sock.recv(length) class RPCStub(object): def __getattr__(self, function): def _func(*args, **kwargs): d = {'method_name': function, 'method_args': args, 'method_kwargs': kwargs} self.send(json.dumps(d).encode('utf-8')) # 发送数据 data = self.recv(1024) # 接收方法执行后返回的结果 return data setattr(self, function, _func) return _func
TCPClient类就是常规的Socket API的操做,无需多言,主要看看RPCStub类。
当咱们在Client端调用res = c.add(1, 2, c=3)
时,会执行RPCStub中的__getattr__
方法,该方法会将Client端调用的方法、参数等信息经过TCPServer类的send方法发送,发送数据进行了JSON格式化,方便Server端解码,随后便调用recv方法等待Server端相应的数据返回。
由于RPCClient类自己没有add方法,为了让用户作到Client端直接调用Server端方法的形式,先利用__getattr__
构建了_func
方法,并将其经过setattr方法设置到RPCClient类中,此时该类就有Server端方法对应的映射了。
调用add方法,就调用了对应的_func
方法,将数据发送至Server端。
Client端就这样搞定了,接着来实现Server端,不用紧张,很是简单。
Server端的使用方式以下。
# server.py import rpcserver def add(a, b, c=10): sum = a + b + c return sum s = rpcserver.RPCServer() s.register_function(add) # 注册方法 s.loop(5000) # 传入要监听的端口
实例化rpcserver.RPCServer类,而后经过register_function方法将想被Client端调用的方法传入,随后调用loop方法,将要监听的端口传入,RPCServer类的实现以下。
# rpcserver.py class RPCServer(TCPServer, JSONRPC, RPCStub): def __init__(self): TCPServer.__init__(self) JSONRPC.__init__(self) RPCStub.__init__(self) def loop(self, port): # 循环监听 5000 端口 self.bind_listen(port) print('Server listen 5000 ...') while True: self.accept_receive_close() def on_msg(self, data): return self.call_method(data)
RPCServer继承自TCPServer、JSONRPC、RPCStub,这些类一样实如今rpcserver.py文件中而且给出了详细的注释,因此就详细解释了。
class TCPServer(object): def __init__(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) def bind_listen(self, port): self.sock.bind(('0.0.0.0', port)) self.sock.listen(5) def accept_receive_close(self): '''获取Client端信息''' (client_socket, address) = self.sock.accept() msg = client_socket.recv(1024) data = self.on_msg(msg) client_socket.sendall(data) # 回传 client_socket.close() class JSONRPC(object): def __init__(self): self.data = None def from_data(self, data): '''解析数据''' self.data = json.loads(data.decode('utf-8')) def call_method(self, data): '''解析数据,调用对应的方法变将该方法执行结果返回''' self.from_data(data) method_name = self.data['method_name'] method_args = self.data['method_args'] method_kwargs = self.data['method_kwargs'] res = self.funs[method_name](*method_args, **method_kwargs) data = {"res": res} return json.dumps(data).encode('utf-8') class RPCStub(object): def __init__(self): self.funs = {} def register_function(self, function, name=None): '''Server端方法注册,Client端只可调用被注册的方法''' if name is None: name = function.__name__ self.funs[name] = function
至此,Client端和Server端都写好了,跑一下吧。
经过上述代码,再次理解一下RPC中这几个重要的概念,理解的是否是深刻了一下。
开源的RPC框架确定不是这么简单的,其中考虑了特别的边界条件以及各类优化,但RPC自己确是简单的。
最近一直在研究Docker,尝试经过Go来写一个玩具docker,后面弄出来,会分享一下Go与docker相关的内容。
下篇文章见,对了,有帮助的话,点「在看」或「赞扬」进行催更吧。