python怎么玩rpc_使用Python实现RPC框架

前言

本文将会使用Python实现一个最简单的RPC框架,玩具向,不具有实用意义,但可以让你清醒的理解RPC框架的几个组成部分,只是比看Python自带的xmlrpc清晰。

本文需要一点Python socket基础。

如果你对Python Socket基础方面的内容不是很熟悉,推荐阅读Real Python的「Socket Programming in Python (Guide)」

吐槽一下VSCode,在开发一些比较复杂的Python项目时,VSCode的debug功能让人感到蛋疼,询问了Windows下使用VSCode的同事,都没有这样的问题,不清楚VSCode对Mac的支持是否存在问题,还只是我单纯的不会用:(

本文代码比较简单,所以还是使用VSCode进行开发。那我们开始吧!

回顾RPC

wAAACwAAAAAAQABAEACAkQBADs=

客户端(Client):服务调用方。

客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。

服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。

服务端(Server):服务的真正提供者。

Network Service:底层传输,可以是 TCP 或 HTTP。

实现jsonrpc

在实现前,简单理一下整体思路。

1.Network Service 直接使用Python Socket相关的API实现

2.传输数据使用JSON,在Socket层会被压成二进制,我们无需关心

模仿xmlrpc,Client与Server都采用Minix多继承机制来实现,每个类负责自身的事情,最终暴露出现的只有一个类中有限的方法。

先从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方法返回的结果打印出了。

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端都写好了,跑一下吧。

wAAACwAAAAAAQABAEACAkQBADs=

总结一下

通过上述代码,再次理解一下RPC中这几个重要的概念,理解的是不是深入了一下。客户端(Client):服务调用方。

客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。

服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。

服务端(Server):服务的真正提供者。

Network Service:底层传输,可以是 TCP 或 HTTP。

开源的RPC框架肯定不是这么简单的,其中考虑了特别的边界条件以及各种优化,但RPC本身确是简单的。

结尾

最近一直在研究Docker,尝试通过Go来写一个玩具docker,后面弄出来,会分享一下Go与docker相关的内容。

下篇文章见,对了,有帮助的话,点「在看」或「赞赏」进行催更吧。

https://juejin.im/post/5e200601f265da3e3e753ff3

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值