万物互联之~RPC专栏

其他专栏最新篇:协程加强之~兼容答疑篇 | 聊聊数据库~SQL环境篇

上篇回顾:万物互联之~深入篇

3.RPC引入

Code:https://github.com/lotapp/BaseCode/tree/master/python/6.net/6.rpc/

3.1.概念

RPC(Remote Procedure Call):分布式系统常见的一种通信方法(远程过程调用),通俗讲:可以一台计算机的程序调用另一台计算机的子程序(可以把它看成之前我们说的进程间通信,只不过这一次的进程不在同一台PC上了)

PS:RPC的设计思想是力图使远程调用中的通讯细节对于使用者透明,调用双方无需关心网络通讯的具体实现

引用一张网上的图:

HTTP有点相似,你可以这样理解:

  1. 老版本的HTTP/1.0是短链接,而RPC是长连接进行通信

    • HTTP协议(header、body),RPC可以采取HTTP协议,也可以自定义二进制格式
  2. 后来HTTP/1.1支持了长连接(Connection:keep-alive),基本上和RPC差不多了
    • keep-alive一般都限制有最长时间,或者最多处理的请求数,而RPC是基于长连接的,基本上没有这个限制
  3. 后来谷歌直接基于HTTP/2.0建立了gRPC,它们之间的基本上也就差不多了
    • 如果硬是要区分就是:HTTP-普通话RPC-方言的区别了
    • RPC高效而小众,HTTP效率没RPC高,但更通用
  4. PS:RPCHTTP调用不用经过中间件,而是端到端的直接数据交互
    • 网络交互可以理解为基于Socket实现的(RPCHTTP都是Socket的读写操作)

简单概括一下RPC的优缺点就是:

  1. 优点:

    1. 效率更高(可以自定义二进制格式)
    2. 发起RPC调用的一方,在编写代码时可忽略RPC的具体实现(跟编写本地函数调用一般
  2. 缺点:
    • 通用性不如HTTP(方言普及程度肯定不如普通话),如果传输协议不是HTTP协议格式,调用双方就需要专门实现通信库

PS:HTTP更多是ClientServer的通讯;RPC更多是内部服务器间的通讯

3.2.引入

上面说这么多,可能还没有来个案例实在,我们看个案例:

本地调用sum()

def sum(a, b):
    """return a+b"""
    return a + b

def main():
    result = sum(1, 2)
    print(f"1+2={result}")

if __name__ == "__main__":
    main()

输出:(这个大家都知道)

1+2=3

1.xmlrpc案例

官方文档:

https://docs.python.org/3/library/xmlrpc.client.html
https://docs.python.org/3/library/xmlrpc.server.html

都说RPC用起来就像本地调用一样,那么用起来啥样呢?看个案例:

服务端:(CentOS7:192.168.36.123:50051)

from xmlrpc.server import SimpleXMLRPCServer

def sum(a, b):
    """return a+b"""
    return a + b

# PS:50051是gRPC默认端口
server = SimpleXMLRPCServer(('', 50051))
# 把函数注册到RPC服务器中
server.register_function(sum)
print("Server启动ing,Port:50051")
server.serve_forever()

客户端:(Win10:192.168.36.144

from xmlrpc.client import ServerProxy

stub = ServerProxy("http://192.168.36.123:50051")
result = stub.sum(1, 2)
print(f"1+2={result}")

输出:(Client用起来是不是和本地差不多?就是通过代理访问了下RPCServer而已)

1+2=3

PS:CentOS服务器不是你绑定个端口就一定能访问的,如果不能记让防火墙开放对应的端口

这个之前在说MariaDB环境的时候有详细说:https://www.cnblogs.com/dotnetcrazy/p/9887708.html#_map4

# 添加 --permanent永久生效(没有此参数重启后失效)
firewall-cmd --zone=public --add-port=80/tcp --permanent

2.ZeroRPC案例:

zeroRPC用起来和这个差不多,也简单举个例子吧:

把服务的某个方法注册到RPCServer中,供外部服务调用

import zerorpc

class Test(object):
    def say_hi(self, name):
        return f"Hi,My Name is{name}"

# 注册一个Test的实例
server = zerorpc.Server(Test())
server.bind("tcp://0.0.0.0:50051")
server.run()

调用服务端代码

import zerorpc

client = zerorpc.Client("tcp://192.168.36.123:50051")
result = client.say_hi("RPC")
print(result)

3.3.简单版自定义RPC

看了上面的引入案例,是不是感觉RPC不过如此?NoNoNo,要是真这么简单也就谈不上RPC架构了,上面两个是最简单的RPC服务了,可以这么说:生产环境基本上用不到,只能当案例练习罢了,对Python来说,最常用的RPC就两个gRPC and Thrift

PS:国产最出名的是Dubbo and Tars,Net最常用的是gRPCThriftSurging

1.RPC服务的流程

要自己实现一个RPC Server那么就得了解整个流程了:

  1. Client(调用者)以本地调用的方式发起调用
  2. 通过RPC服务进行远程过程调用(RPC的目标就是要把这些步骤都封装起来,让使用者感觉不到这个过程)
    1. 客户端的RPC Proxy组件收到调用后,负责将被调用的方法名、参数等打包编码成自定义的协议
    2. 客户端的RPC Proxy组件在打包完成后通过网络把数据包发送给RPC Server
    3. 服务端的RPC Proxy组件把通过网络接收到的数据包按照相应格式进行拆包解码,获取方法名和参数
    4. 服务端的RPC Proxy组件根据方法名和参数进行本地调用
    5. RPC Server(被调用者)本地执行后将结果返回给服务端的RPC Proxy
    6. 服务端的RPC Proxy组件将返回值打包编码成自定义的协议数据包,并通过网络发送给客户端的RPC Proxy组件
    7. 客户端的RPC Proxy组件收到数据包后,进行拆包解码,把数据返回给Client
  3. Client(调用者)得到本次RPC调用的返回结果

用一张时序图来描述下整个过程:

PS:RPC Proxy有时候也叫Stub(存根):(Client Stub,Server Stub)

为屏蔽客户调用远程主机上的对象,必须提供某种方式来模拟本地对象,这种本地对象称为存根(stub),存根负责接收本地方法调用,并将它们委派给各自的具体实现对象

PRC服务实现的过程中其实就两核心点:

  1. 消息协议:客户端调用的参数和服务端的返回值这些在网络上传输的数据以何种方式打包编码和拆包解码

    • 经典代表:Protocol Buffers
  2. 传输控制:在网络中数据的收发传输控制具体如何实现(TCP/UDP/HTTP

2.手写RPC

下面我们就根据上面的流程来手写一个简单的RPC:

1.Client调用:

# client.py
from client_stub import ClientStub

def main():
    stub = ClientStub(("192.168.36.144", 50051))

    result = stub.get("sum", (1, 2))
    print(f"1+2={result}")

    result = stub.get("sum", (1.1, 2))
    print(f"1.1+2={result}")

    time_str = stub.get("get_time")
    print(time_str)

if __name__ == "__main__":
    main()

输出:

1+2=3
1.1+2.2=3.1
Wed Jan 16 22

2.Client Stub,客户端存根:(主要有打包解包、和RPC服务器通信的方法)

# client_stub.py
import socket

class ClientStub(object):
    def __init__(self, address):
        """address ==> (ip,port)"""
        self.socket = socket.socket()
        self.socket.connect(address)

    def convert(self, obj):
        """根据类型转换成对应的类型编号"""
        if isinstance(obj, int):
            return 1
        if isinstance(obj, float):
            return 2
        if isinstance(obj, str):
            return 3

    def pack(self, func, args):
        """打包:把方法和参数拼接成自定义的协议
        格式:func:函数名@params:类型-参数,类型2-参数2...
        """
        result = f"func:{func}"
        if args:
            params = ""
            # params:类型-参数,类型2-参数2...
            for item in args:
                params += f"{self.convert(item)}-{item},"
            # 去除最后一个,
            result += f"@params:{params[:-1]}"
        # print(result)  # log 输出
        return result.encode("utf-8")

    def unpack(self, data):
        """解包:获取返回结果"""
        msg = data.decode("utf-8")
        # 格式应该是"data:xxxx"
        params = msg.split(":")
        if len(params) > 1:
            return params[1]
        return None

    def get(self, func, args=None):
        """1.客户端的RPC Proxy组件收到调用后,负责将被调用的方法名、参数等打包编码成自定义的协议"""
        data = self.pack(func, args)
        # 2.客户端的RPC Proxy组件在打包完成后通过网络把数据包发送给RPC Server
        self.socket.send(data)
        # 等待服务端返回结果
        data = self.socket.recv(2048)
        if data:
            return self.unpack(data)
        return None

简要说明下:(我根据流程在Code里面标注了,看起来应该很轻松)

之前有说到核心其实就是消息协议and传输控制,我客户端存根的消息协议是自定义的格式(后面会说简化方案):func:函数名@params:类型-参数,类型2-参数2...,传输我是基于TCP进行了简单的封装



3.Server端:(实现很简单)

# server.py
import socket
from server_stub import ServerStub

class RPCServer(object):
    def __init__(self, address, mycode):
        self.mycode = mycode
        # 服务端存根(RPC Proxy)
        self.server_stub = ServerStub(mycode)
        # TCP Socket
        self.socket = socket.socket()
        # 端口复用
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定端口
        self.socket.bind(address)

    def run(self):
        self.socket.listen()
        while True:
            # 等待客户端连接
            client_socket, client_addr = self.socket.accept()
            print(f"来自{client_addr}的请求:\n")
            # 交给服务端存根(Server Proxy)处理
            self.server_stub.handle(client_socket, client_addr)

if __name__ == "__main__":
    from server_code import MyCode
    server = RPCServer(('', 50051), MyCode())
    print("Server启动ing,Port:50051")
    server.run()

为了简洁,服务端代码我单独放在了server_code.py中:

# 5.RPC Server(被调用者)本地执行后将结果返回给服务端的RPC Proxy
class MyCode(object):
    def sum(self, a, b):
        return a + b

    def get_time(self):
        import time
        return time.ctime()

4.然后再看看重头戏Server Stub:

# server_stub.py
import socket

class ServerStub(object):
    def __init__(self, mycode):
        self.mycode = mycode

    def convert(self, num, obj):
        """根据类型编号转换类型"""
        if num == "1":
            obj = int(obj)
        if num == "2":
            obj = float(obj)
        if num == "3":
            obj = str(obj)
        return obj

    def unpack(self, data):
        """3.服务端的RPC Proxy组件把通过网络接收到的数据包按照相应格式进行拆包解码,获取方法名和参数"""
        msg = data.decode("utf-8")
        # 格式应该是"格式:func:函数名@params:类型编号-参数,类型编号2-参数2..."
        array = msg.split("@")
        func = array[0].split(":")[1]
        if len(array) > 1:
            args = list()
            for item in array[1].split(":")[1].split(","):
                temps = item.split("-")
                # 类型转换
                args.append(self.convert(temps[0], temps[1]))
            return (func, tuple(args))  # (func,args)
        return (func, )

    def pack(self, result):
        """打包:把方法和参数拼接成自定义的协议"""
        # 格式:"data:返回值"
        return f"data:{result}".encode("utf-8")

    def exec(self, func, args=None):
        """4.服务端的RPC Proxy组件根据方法名和参数进行本地调用"""
        # 如果没有这个方法则返回None
        func = getattr(self.mycode, func, None)
        if args:
            return func(*args)  # 解包
        else:
            return func()  # 无参函数

    def handle(self, client_socket, client_addr):
        while True:
            # 获取客户端发送的数据包
            data = client_socket.recv(2048)
            if data:
                try:
                    data = self.unpack(data)  # 解包
                    if len(data) == 1:
                        data = self.exec(data[0])  # 执行无参函数
                    elif len(data) > 1:
                        data = self.exec(data[0], data[1])  # 执行带参函数
                    else:
                        data = "RPC Server Error Code:500"
                except Exception as ex:
                    data = "RPC Server Function Error"
                    print(ex)
                # 6.服务端的RPC Proxy组件将返回值打包编码成自定义的协议数据包,并通过网络发送给客户端的RPC Proxy组件
                data = self.pack(data)  # 把函数执行结果按指定协议打包
                # 把处理过的数据发送给客户端
                client_socket.send(data)
            else:
                print(f"客户端:{client_addr}已断开\n")
                break

再简要说明一下:里面方法其实主要就是解包执行函数返回值打包

输出图示:

再贴一下上面的时序图:

课外拓展:

HTTP1.0、HTTP1.1 和 HTTP2.0 的区别
https://www.cnblogs.com/heluan/p/8620312.html

简述分布式RPC框架
https://blog.csdn.net/jamebing/article/details/79610994

分布式基础—RPC
http://www.dataguru.cn/article-14244-1.html

下节预估:RPC服务进一步简化与演变手写一个简单的REST接口

原文地址:https://www.cnblogs.com/dunitian/p/10279946.html

时间: 2024-10-09 17:07:34

万物互联之~RPC专栏的相关文章

万物互联之~网络编程深入篇

深入篇¶ 上节回顾:5种IO模型 | IO多路复用 and 万物互联之-网络编程加强篇 官方文档:https://docs.python.org/3/library/internet.html 1.概念回顾¶ 1.1.TCP三次握手¶ 画一张图来通俗化讲讲TCP三次握手: 用代码来说,大概过程就是: 1.2.TCP四次挥手¶ 画图通俗讲下TCP四次挥手: 用代码来说,大概过程就是: 其实这个也很好的解释了之前的端口占用问题,如果是服务端先断开连接,那么服务器就是四次挥手的发送方,最后一次消息是得

握有一手好牌的商业WiFi,能否成为万物互联的“入口”?

在固定互联网时代,入口是浏览器的导航站以及搜索栏,打下百度的辉煌:在移动互联网时代,被称之为入口有App store(应用的入口)和微信(社交和自媒体的入口),他们是助推了苹果和腾讯分别成为全球.亚洲估值最大的公司. 伴随人们上网时间越来越长,流量费反超通话费开支,大家都希望连WiFi上网, WiFi甚至被当做生理需求.在进店消费中,WiFi已成为招揽人气的标配.但WiFi目前还不能称之为入口,要成为入口必须符合两个条件:一是不可替代.跨不过去:二是商业价值大.潜力无限. 很多人即使不安装任何W

数据在我们生活中的应用,万物互联

当下是属于移动互联网高速发展的时代,移动互联网深入到生活的方法面面,通过手机加上联网,就可以完完全全实现吃.穿.住.行生活方方面面的购买服务和享受服务的需求.单互联网并不仅仅如此,互联网并不是虚拟的,而是现实世界数据的处理中心,无论订单.购买记录.汽车导航.智能化推荐等等个性化的信息无一不在体现着我们享受着信息化带来的便利. 我们似乎觉得还不够,我们虽然可以通过手机获取各种各样的服务,但是需要安装各种各样的手机app,每次不同的需求都需要精确的下载并打开注册对应的app才能享受服务,每个不同的需

吴军博士:物联网和人工智能将再造一个英特尔和微软 | 万物互联

创新大会 本文作者:程弢 2016-11-13 13:57 导语:吴军认为,在人工智能或者物联网领域,谁能把操作系统问题解决了,谁就是下一个Google和微软:谁把处理器问题解决好了,谁就是下一个英特尔和高通. 编者按:今日上午,由B12主办的第二届万物互联创新大会在杭州召开,丰元创投创始合伙人  .硅谷风险投资人.<浪潮之巅>作者吴军博士做了一场关于人工智能非常务实.接地气的演讲. 今年是人工智能概念诞生60周年,在这一年人工智能行业也发生了几件大事:今年1月,1956年提出人工智能概念的科

2016 ASC 移动物联网安全高峰论坛 万物互联时代的安全与隐私

互联网的发展已经迈入了“万物互联”时代.移动设备作为人.物连接的主要入口,让人们享受高效.便利的“互联生活”的同时,也给用户的安全和隐私带来了前所未有的挑战.正是在这样的背景下,移动互联网安全技术国家工程实验室携手SecZone,并联合OWASP中国.ASC应用安全联盟.CSA云安全联盟特别组织策划了首届ASC移动物联网高峰论坛. 主题:万物互联时代的安全与隐私 会议形式:移动物联网主题演讲 时间:2016-10-25 至 2016-10-26 地点:深圳 深圳虚拟大学园 快捷报名:http:/

软交所--万物互联 大数据成新威胁

互联网宽带.移动技术高速发展,人们在畅享网络生活高效.便利.快捷的同时,却也面临着日趋严重的网络信息安全威胁和隐患. 本周三,亚太信息安全领域最权威的年度峰会--2014中国互联网安全大会(ISC 2014)召开.本届大会的主题为"互联世界,安全第一",大会聚焦互联网时代.大数据背景下的信息安全所面临的全新挑战和问题,深入探讨了智慧城市.互联网金融.数字医疗.可穿戴计算等业界关心的问题. 360董事长周鸿祎认为,给信息安全管理体系带来了巨大冲击,传统的系统安全.边界安全等,已无法防卫以

万物互联下的企业新生态

1)隐私是相对的,没有绝对的隐私,只有相对的隐私,隐私是建立在道德和法律之上的隐私:2)商业信息的进一步透明化,企业对于客户资料的保密性(非隐私部分)将大大降低.      自古以来,商业利用的就是两点(权力之类的特殊性不考虑):信息不对称和资源区域分布的不均衡.但随着万物互联,靠信息不对称来赚钱的商业模式将面临着重大的挑战,而资源分布不平衡还会继续存在,但也会随着物流的发展而趋向平衡.这样发展的一个结果就是商家想留住客户就不能靠客户资料保密来完成,因为竞争者很容易从网上获取这些客户的资料.这时

《未来简史》十一、终局,万物互联与数据主义

前情回顾 先对之前做一个简单的回顾. 首先,科技浪潮击碎了人文主义的根基,我们根本就没有自由意志,一切都是算法的结果.我们每个人也并非是一个不可分割的自我,在我们的大脑中每一种情绪都是一种声音,他们争论不休,但是我们自己却感知不到.我们大脑中有两种自我形式,一种叫做体验自我,它是客观的,把我们当下的体验直接汇报给我们.但是我们认同的多半是另一个,叙事自我.叙事自我不断的给我们编制各种各样的故事,让我们的生活能够逻辑自洽,也把我们自己一辈子困在这些的故事里面.我们这才意识到,原来我们所谓的自我,所

5G下的区块链YottaChain如何助力万物互联发展?

5G通信.区块链技术与物联网设备相衔接,能为社会共享非常大的价值.几年来,专家们早已留意到物联网能给我们社会带来的潜力.但是,两大瓶颈限制了它的开展:安全和高效.在万物互联的场景下,机器类通信.大规模通信.关键性任务的通信对网络的速率.稳定性.时延等提出更高的要求,这些新应用对 5G的需求十分迫切.但假如连入5G高速网络,物联网设备就能够取得愈加广泛的应用. 物联网需求高速度.低延迟的网络,延迟是指信号从发出到接纳的时间距离.低延迟对设备而言十分重要,可确保快速沟通,防止明显滞后.4G网络而言,