1前言
此前,我在做跨语言调用时,用的是 Facebook 的 Thrift,挺轻量的,还不错。
Thrift是一种接口描述语言和二进制通讯协议,它被用来定义和创建跨语言的服务。它被当作一个远程过程调用(RPC)框架来使用,是由Facebook为“大规模跨语言服务开发”而开发的。它通过一个代码生成引擎联合了一个软件栈,来创建不同程度的、无缝的跨平台高效服务,可以使用C#、C++(基于POSIX兼容系统)Cappuccino、Cocoa、Delphi、Erlang、Go、Haskell、Java、Node.js、OCaml、Perl、PHP、Python、Ruby和Smalltalk编程语言开发。 2007由Facebook开源,2008年5月进入Apache孵化器, 2010年10月成为Apache的顶级项目。
最近的项目中也有类似的需求,这次打算使用 Google 的 gRPC,原因是 gRPC 知名度也很高,之前一直想用但没有场景,加上 AspNetCore 的文档里有介绍,看起来官方也是推荐使用这种方式进行 RPC 调用。.
所以就果断开始了 gRPC 实践,最终做出来的效果还是可以的(这是后话)。
本文主要介绍 C# 与 Python 之间,使用 gRPC 进行跨语言调用。
2概念科普
什么是RPC?
远程过程调用(Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。 比如 Java RMI(远程方法调用(Remote Method Invocation)。能够让在某个java虚拟机上的对象像调用本地对象一样调用另一个java虚拟机中的对象上的方法)。

从上图可以看出, RPC 本身是 client-server模型,也是一种 request-response 协议。 有些实现扩展了远程调用的模型,实现了双向的服务调用,但是不管怎样,调用过程还是由一个客户端发起,服务器端提供响应,基本模型没有变化。 服务的调用过程为:
client调用client stub,这是一次本地过程调用 client stub将参数打包成一个消息,然后发送这个消息。打包过程也叫做 marshalling client所在的系统将消息发送给server server的的系统将收到的包传给server stub server stub解包得到参数。 解包也被称作 unmarshalling 最后server stub调用服务过程. 返回结果按照相反的步骤传给client
关于 gRPC
gRPC 是一种与语言无关的高性能远程过程调用 (RPC) 框架。
前文有说过,这个是 Google 开发的,以下是 AspNetCore 的文档中,对 gRPC 的介绍。
gRPC 的主要优点是:
现代高性能轻量级 RPC 框架。 协定优先 API 开发,默认使用协议缓冲区,允许与语言无关的实现。 可用于多种语言的工具,以生成强类型服务器和客户端。 支持客户端、服务器和双向流式处理调用。 使用 Protobuf 二进制序列化减少对网络的使用。 这些优点使 gRPC 适用于:
效率至关重要的轻量级微服务。 需要多种语言用于开发的 Polyglot 系统。 需要处理流式处理请求或响应的点对点实时服务。
gRPC 使用 .proto
文件来定义接口和数据类型,同时提供了通过 .proto
文件生成不同语言调用代码的工具。
3项目介绍
本文的项目是使用 C# 调用 Python 写的服务,这个服务是一个大语言模型。
在本项目中,C# 项目作为客户端,Python 项目作为服务端。
4编写 .proto
文件
第一步,要先编写用于定义接口和数据结构的 .proto
文件。
将下面代码保存到 chat.proto
文件里,接下来在要接入 gRPC 的每个项目里,都要复制一份这个文件。
syntax = "proto3";
import "google/protobuf/wrappers.proto";
option csharp_namespace = "AIHub.Blazor";
package aihub;
service ChatHub {
rpc Chat (ChatRequest) returns (ChatReply);
rpc StreamingChat (ChatRequest) returns (stream ChatReply);
}
message ChatRequest {
string prompt = 1;
repeated google.protobuf.StringValue history = 2;
int32 max_length = 3;
float top_p = 4;
float temperature = 5;
}
message ChatReply {
string response = 1;
}
这里定义了两个 Chat 方法,其中一个是一元调用,一个是流式输出。
gRPC 服务可以有不同类型的方法,服务发送和接收消息的方式取决于所定义的方法的类型
-
一元 - 调用完就返回,同步方法 -
服务器流式处理 - 服务端流式返回数据 -
客户端流式处理 - 客户端流式写入数据 -
双向流式处理
都挺好理解的,在 proto
定义里面也很易懂,stream
放在哪里,哪里就是流式。
stream
放在请求参数,那客户端输入就是流式的,放在返回值前面,那服务端的返回就是流式。都放就代表双向流式。
接着又定义了用到的数据结构,一个输入参数 ChatRequest
,一个返回参数 ChatReply
。
string prompt = 1;
这里赋值的意思是这个参数的位置,不是定义变量的赋值