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)环境中运行。

 

posted @ 2025-04-17 22:49  decode-dev  阅读(392)  评论(0)    收藏  举报