C#简单封装gRPC类库以方便调用
最近开始学习在C#中使用gRPC,参考文章C#封装GRPC类库及调用简单实例,记录一下使用过程。
1、创建类库
创建类库工程,主要目的是封装RPC调用过程,将RPC相关功能抽离出来,如果想改为用其他的RPC(比如Thrift)也比较方便。
1.1 创建类库工程
创建类库的工程“Lib_gRpc”,在nuget程序包中添加三个相关的包:“Google.Protobuf”、“Grpc.Core”、“Grpc.Tools”:

1.2 编写接口
按照业务逻辑所需要的接口,新建原型文件“CommonTestService.proto”,参考相关语法手动编写接口:
使用proto定义调用接口
syntax = "proto3";
package Lib_gRpc;
// 指定了生成的 C# 代码的命名空间为 LinkService,生成的类将位于 LinkService 命名空间中
option csharp_namespace = "LinkService";
//定义了一个名为 CommonTestService 的 gRPC 服务。在 gRPC 中,服务是由一个或多个 RPC 方法组成的
service CommonTestService
{
// 定义一个名为GetInfo的RPC方法,以gRpcCommonRequest类型的消息作为参数,返回gRpcCommonReply类型的消息
rpc GetInfo (gRpcCommonRequest) returns (gRpcCommonReply);
}
// 输入参数
message gRpcCommonRequest
{
string RequestName = 1;
}
// 输出参数
message gRpcCommonReply
{
int32 ResultType = 1;
string ResultMessage = 2;
}
主要是定义了服务端提供的接口“GetInfo”。
本来应该在工程中使用gRPC工具编译就可以,但是不知道为什么出这个错:
error MSB6006: “(gRPC.Tools的安装路径)\windows_x64\protoc.exe”已退出,代码为 -1073741790。
gRPC是第一次使用,暂时找不到原因以及解决方法,先改用手动生成吧:
(gRPC.Tools的安装路径)\windows_x86\protoc.exe --csharp_out cs --grpc_out cs --plugin=protoc-gen-grpc=(gRPC.Tools的安装路径)\windows_x86\grpc_csharp_plugin.exe CommonTestService.proto
rem 各参数意义:
rem -I --proto_path 指定导入的路径,如不指定则为当前工作目录
rem --csharp_out 生成C#源文件
rem --grpc_out grpc类的输出路径
rem --plugin==protoc-gen-grpc=......\grpc_csharp_plugin.exe 指定插件及路径
运行这个命令,在cs目录生成了两个C#代码的类文件。
1.3 封装服务端和客户端的相关操作
Server端相关操作封装的类
/// <summary>
/// 服务端相关功能
/// </summary>
public class LinkServer
{
/// <summary>
/// 用于服务端回复的委托(返回类型不能直接使用gRpcCommonReply?)
/// </summary>
static public Func<string, RPCReply> ReplyMessage;
// 定义服务端和客户端
private Server link_server;
/// <summary>
/// 启动服务
/// </summary>
/// <param name="host"></param>
/// <param name="port"></param>
public void Start(string host, int port)
{
link_server = new Server
{
Services = { CommonTestService.BindService(new LinkServiceImpl()) },
Ports = { new ServerPort(host, port, ServerCredentials.Insecure) }
};
link_server.Start();
}
/// <summary>
/// 关闭服务
/// </summary>
public void Close()
{
link_server?.ShutdownAsync().Wait();
}
}
为了方便模块化,这里使用了一个委托,供外部用于这实现回复函数。
新建一个类,为服务的抽象类CommonTestServiceBase实现在proto文件定义的接口GetInfo,这里直接调用服务端类的委托就可以了。
为服务端实现proto文件定义的接口
public class LinkServiceImpl : CommonTestServiceBase
{
/// <summary>
/// 重写在proto文件中,为服务端中定义的方法,在这里调用服务端的委托函数
/// </summary>
/// <param name="request"></param>
/// <param name="context"></param>
/// <returns></returns>
public override Task<gRpcCommonReply> GetInfo(gRpcCommonRequest request, ServerCallContext context)
{
if (LinkServer.ReplyMessage == null)
{
return base.GetInfo(request, context); // 产生未实现的异常
}
else
{
gRpcCommonReply reply = new gRpcCommonReply();
RPCReply ret = new RPCReply();
ret = LinkServer.ReplyMessage(request.RequestName);
reply.ResultType = ret.ResultType;
reply.ResultMessage = ret.ResultMessage;
return Task.FromResult(reply);
}
}
}
Client相关操作封装的类
/// <summary>
/// 客户端相关功能
/// </summary>
public class LinkClient
{
private CommonTestService.CommonTestServiceClient link_client;
private Channel channel;
/// <summary>
/// 启动客户端
/// </summary>
/// <param name="server_host"></param>
public void Start(string server_host)
{
channel = new Channel(server_host, ChannelCredentials.Insecure);
link_client = new CommonTestService.CommonTestServiceClient(channel);
}
/// <summary>
/// 关闭客户端
/// </summary>
public void Close()
{
channel.ShutdownAsync().Wait();
}
/// <summary>
/// 客户端发送消息
/// </summary>
/// <param name="cmd"></param>
/// <returns></returns>
public RPCReply GetInfo(string cmd)
{
gRpcCommonRequest request = new gRpcCommonRequest();
request.RequestName = cmd;
gRpcCommonReply reply = link_client.GetInfo(request);
RPCReply ret = new RPCReply();
ret.ResultType = reply.ResultType;
ret.ResultMessage = reply.ResultMessage;
return ret;
}
2、服务端程序
新建一个控制台项目,引用第一步创建的类库工程。
这里主要功能是创建后台服务,和实现在前面定义接口的功能。
服务端主函数代码
static void Main(string[] args)
{
const string _host = "localhost";
const int _port = 20001;
//Server server = new Server
//{
// Services = { CommonTest.BindService(new CommonTestImpl()) },
// Ports = { new ServerPort(_host, _port, ServerCredentials.Insecure) }
//};
//server.Start();
LinkServer server = new LinkServer();
LinkServer.ReplyMessage = ReplyMessage;
server.Start(_host, _port);
Console.WriteLine($"已开启服务(端口:{_port})");
Console.WriteLine($"输入回车结束服务。。。");
Console.ReadLine();
//server.ShutdownAsync().Wait();
server.Close();
}
在这里调用服务端创建的功能,就不用关心gRpc的相关细节了,也不用直接在nuget中添加gRPC相关的包。
接口的逻辑功能代码
static public RPCReply ReplyMessage(string cmd)
{
RPCReply reply = new RPCReply();
//LinkService.gRpcCommonReply reply = new LinkService.gRpcCommonReply(); // 返回类型不能直接使用gRpcCommonReply?
reply.ResultType = 0;
switch (cmd)
{
case "Version":
reply.ResultType = 1;
reply.ResultMessage = "V1.0 20250416";
break;
default:
reply.ResultMessage = "Unknow CMD";
break;
}
return reply;
}
3、gRPC服务的测试
能支持gRPC的测试工具比较多,比如ApiFox,使用起来跟HTTP差不多,对于前后端分离的开发,这无疑是一个更加方便的工作方式。
在ApiFox中新建一个gRPC项目,按照新建向导或者手动添加,就可以导入proto的接口文件。
导入接口文件以后,可以看到GetInfo这个接口,还可以自动生成输入参数。
配置好环境的服务器信息,就可以直接调用接口进行测试了。

4、客户端调用服务
新建一个客户端WinForm工程,引用第一步创建的类库工程,就可以很方便的调用服务端提供的服务了。
LinkClient client = new LinkClient();
client.Start("127.0.0.1:20001");
var ret = client.GetInfo("Version");
client.Close();
MessageBox.Show(ret.ResultMessage);
5、使用Win7运行测试
几个工程都是在VS2022环境,使用C#创建的.net9.0的项目,使用AOT方式发布,目标运行时选择“win-x86”,部署模式选“独立”,服务端和客户端程序,都可以直接在没有安装.net9环境的32位Win7(SP1)环境中运行。


浙公网安备 33010602011771号