RPC原理

远程过程调用(英语:Remote Procedure Call,缩写为 RPC,也叫远程程序调用)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。如果涉及的软件采用面向对象编程,那么远程过程调用亦可称作远程调用或远程方法调用。
广义
我们可以将所有通过网络来进行通讯调用的实现统称为RPC。按照这样来理解的话,那我们发现HTTP其实也算是一种RPC实现。
狭义
区别于HTTP的实现方式,在传输的数据格式上和传输的控制上独立实现。比如在机器间通讯传输的数据不采用HTTP协议的方式(分为起始行、header、body三部份),而是使用自定义格式的二进制方式。

通俗理解:

就是一台计算机程序A想远程去调用另一台计算机的程序B,需要使用的协议,就是RPC。

firda rpc 远程调用教程 rpc远程调用技术_服务器


实现:Client发起调用后挂起,Server继续处理业务并将结果再返回给Client,Client收到结果后再继续处理接下来的业务。

传递:因为语言不同,所以需要先将语言序列化,在进行通讯,使用的辅助工具是:Client stub(客户端存根)和Server stub(服务端存根)

gRPC框架

通俗理解:就是根据RPC原理,封装成的一个工具,用来实现跨计算机的程序调用。

1,gRPC框架的使用方法
1,使用Protocol Buffers(proto3)的IDL((交互式数据语言Interactive Data Language))接口定义语言定义接口服务,编写在文本文件(以.proto为后缀名)中。
(1)Protocol Buffers:可扩展机制,用于序列化结构化数据,可以一次定义结构化的数据
(2).proto文档编写结构:
	使用syntax关键字声明proto版本(我们现在用的是proto3), 默认是proto2---->syntax = "proto3";
	使用message关键字定义数据(请求类+响应类):生成python代码就是python类
	使用service定义一个服务类(可以调用的方法类)
2,使用protobuf编译器生成服务器和客户端使用的stub代码
(1)先安装工具: pip install grpcio-tools
(2)终端命令编译proto文件生成python代码文件(xx.proto表示自己建的.proto文件名):
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. xx.proto
(3)命令会生成两个新的文件是服务器和客户端使用的stub代码(xx_pb2.py和xx_pb2_grpc.py)
	xx_pb2.py   保存根据接口定义文件中的数据类型:message定义的,生成python源码,就是一个python的类,
	xx_pb2_grpc.py	保存根据接口定义文件中的服务:service 定义的rpc类,也就是需要被调用的那个类,放在_rpc.py中
3,新建.py文件编写补充服务器
(1)导包
(2)自定义服务类,并继承grpc中的服务类,重写需要被调用的方法:方法中的参数从request中获取,返回响应体对象:【先创建响应体对象,组装返回数据】
(3)创建rpc服务对象:使用server =grpc.server()方法,设置可以服务的线程数
(4)向服务器添加被调用的服务方法
(5)绑定ip和端口:server.add_insecure_port()
(6)启动rpc服务器:server.start()
(7)防止阻塞的代码:while 1:
4,新建.py文件编写客户端逻辑代码
(1)导包
(2)创建一个可以跟推荐系统通讯的通道。grpc.insecure_channel()
(3)创建存根stub
(4)构建请求参数对象
(5).rpc调用
(6)处理结果
2,使用grpc实现需求:创建一个Hello类,有say方法:

d01_xuexi.proto 文件

//声明proto版本
syntax = "proto3";

// 定义请求类,有一个name属性,nums属性
message Request{
    // 添加参数。参数必须声明类型,也必须加上字段编号。数据类型 参数名=字段编号
    // 生成python代码后,就是类的属性。
	string name=1;
    // repeated 字段表示可重复,生成python代码后,属性的类型就是列表
    repeated int32 nums=2;
}

// 定义一个响应类,有一个msg属性
message Response{
	string msg=1;
}

//使用service定义一个服务类
//定义Hello类,有一个say方法,接收Request对象,返回Response对象
// Request和Response都需要提前定义
service Hello{
	rpc say(Request) returns(Response) {}
}

server.py 文件:编辑服务器代码

# 导包
from concurrent.futures import ThreadPoolExecutor 	# 导入线程
import grpc		# 导入grpc
import time		# 导入时间,用于代码阻塞时候维护CPU

# 导入命令生成的stub文件
import d01_xuexi_pb2_grpc		
import d01_xuexi_pb2

# 自定义服务类,并继承grpc中的服务类,重写需要被调用的方法say方法
class HelloService(d01_xuexi_pb2_grpc.HelloServicer):
    def say(self, request, context):
        # request是一个Request对象,可以直接从中获取参数
        name=request.name
        print(name)  # 打印客户端传过来的参数

        # 返回响应体对象:数据保存在d01_xuexi_pb2,所以是d01_xuexi_pb2.Response()
        response = d01_xuexi_pb2.Response()
        response.msg = 'hello {}'.format(name)
        print(response)    #response是一个字典,key一定要命名msg: msg: "hello laowang"
        return response

# 创建rpc服务器对象my_server,可以最多服务20个线程
my_server=grpc.server(ThreadPoolExecutor(max_workers=20))

# 向服务器添加被调用的服务方法(方法保存在d01_xuexi_pb2_grpc中)
# 参数1:自定义服务类对象
# 参数2:服务器对象
d01_xuexi_pb2_grpc.add_HelloServicer_to_server(HelloService(),my_server)

# 绑定安全ip和端口:insecure安全的意思
my_server.add_insecure_port("127.0.0.1:8888")

# 启动服务
my_server.start()

# 添加防止阻塞代码
while 1:
    time.sleep(5*60)

client.py 文件

# 导包
import grpc

import d01_xuexi_pb2_grpc
import d01_xuexi_pb2

# 创建可以跟服务器通讯的通道, ip和端口号要跟server设置的一致
channel=grpc.insecure_channel("127.0.0.1:8888")

# 使用通道,创建存根(stub), 助手
client_stub=d01_xuexi_pb2_grpc.HelloStub(channel)

# 构建请求对象, 并添加参数(数据保存在d01_xuexi_pb2)
my_request=d01_xuexi_pb2.Request()
# 新添加的属性名字,必须跟定义接口时的一样
my_request.name="laowang"

# 使用stub 调用服务器方法
my_response = client_stub.say(my_request)

# 处理返回值
print(my_response)