.NET Core 使用 grpc 实现微服务

GRPC 是Google发布的一个开源、高性能、通用RPC(Remote Procedure Call)框架。提供跨语言、跨平台支持。以下以一个.NET Core Console项目演示如何使用GRPC框架。

一、定义服务

通过proto定义一个数学计算服务,其中包括两个服务方法(Add, Multipy)以及4个请求响应对象(AddRequest, AddReply, MultiplyRequest, MultiplyReply)。

// 文件名:mathservice.proto

syntax = "proto3";
option java_multiple_files = false;
option java_package = "MathServices";
option java_outer_classname = "MathServicesProto";
option objc_class_prefix = "MathServices";
package MathServices;

// 数学运算服务
service MathService 
{
  rpc Add (AddRequest) returns (AddReply) {}
  rpc Multiply (MultiplyRequest) returns (MultiplyReply) {}
}
message AddRequest {
  double First = 1;
  double Second = 2;
}
message AddReply {
  double Sum = 1;
}
message MultiplyRequest {
  double First = 1;
  double Second = 2;
}
message MultiplyReply {
  double Result = 1;
}

二、将服务编译成存根(stub)

通过以下批处理命令generate_protos.bat将服务定义生成多种语言和平台版本的客户端和服务端存根。

@rem 生成客户端和服务器端存根

setlocal

@rem 进入当前目录
cd /d %~dp0

set TOOLS_PATH=C:\Users\Freeman\.nuget\packages\Grpc.Tools\1.0.0\tools\windows_x86

%TOOLS_PATH%\protoc.exe ^
--proto_path protos ^
--cpp_out=Interfaces/cpp ^
--csharp_out=Interfaces/csharp ^
--java_out=Interfaces/java ^
--js_out=Interfaces/javascript ^
--grpc_out=Interfaces/csharp ^
--plugin=protoc-gen-grpc=%TOOLS_PATH%\grpc_csharp_plugin.exe ^
protos/mathservice.proto

endlocal
timeout 5

针对CSHARP语言,protoc.exe编译器生成了如下图几个类,其中左边4个类用于构造请求和响应对象,MathService类用于下一步构造服务和消费服务。


CSHARP STUBS

三、实现并运行服务

通过上一步的编译,自动生成了MathService类,下面通过该类构造并启动grpc服务。

通过继承基类实现服务接口

    /// <summary>
    /// 实现RPC服务端接口。
    /// </summary>
    public class MathServiceImpl : MathService.MathServiceBase
    {
        public override Task<AddReply> Add(AddRequest request, ServerCallContext context)
        {
            return Task.FromResult(new AddReply { Sum = request.First + request.Second });
        }

        public override Task<MultiplyReply> Multiply(MultiplyRequest request, ServerCallContext context)
        {
            return Task.FromResult(new MultiplyReply { Result = request.First * request.Second });
        }
    }

启动服务

const string ip = "0.0.0.0";
const int port = 50051;
Server server = new Server();
server.Ports.Add(new ServerPort(ip, port, ServerCredentials.Insecure));
server.Services.Add(MathService.BindService(new MathServiceImpl()));
server.Start();
server.Ports.ToList().ForEach(a => Console.WriteLine($"Server listening on port {a.Port}..."));
Console.ReadLine();

四、客户端调用服务

客户端通过创建一个Channel和一个服务客户端来使用服务。

var channel = new Channel($"{"127.0.0.1"}:{port}", SslCredentials.Insecure);
var client = new MathService.MathServiceClient(channel);
var random = new Random();

while (true)
{
    var first = random.NextDouble();
    var second = random.NextDouble();
    var reply = client.Add(new AddRequest { First = first, Second = second });
    Console.WriteLine($"RPC call Add service: {first:F4} + {second:F4} = {reply.Sum:F4}");
    Thread.Sleep(500);
} 
RPC调用

五、使用SSL实现加密通讯

grpc默认实现了基于证书的SSL加密通讯,使用中需要注意以下事项。

  • 在Windows上开发请安装 OpenSSL对应版本并将openssl.exe所在路径添加到环境变量中。

  • 通过以下样例脚本生成通讯中所需要的服务端和客户端证书,其中需要特别注意的是,Generate server signing request:中的CN=KEKYK字段如果是本机测试,请一定使用本机名称,如果是真实环境请使用域名,因为客户端必须通过机器名(本地测试)或域名访问该服务。如果此处CN字段不使用机器名或域名,将导致以下错误:


    CN字段不使用主机名或域名时产生的错误
  • 生成服务端和客户端证书脚本generate_ssl.bat

@echo off
set OPENSSL_CONF=c:\OpenSSL-Win64\bin\openssl.cfg   

echo Generate CA key:
openssl genrsa -passout pass:1111 -des3 -out ca.key 4096

echo Generate CA certificate:
openssl req -passin pass:1111 -new -x509 -days 365 -key ca.key -out ca.crt -subj  "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=MyRootCA"

echo Generate server key:
openssl genrsa -passout pass:1111 -des3 -out server.key 4096

echo Generate server signing request:
openssl req -passin pass:1111 -new -key server.key -out server.csr -subj  "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=kekyk"

echo Self-sign server certificate:
openssl x509 -req -passin pass:1111 -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt

echo Remove passphrase from server key:
openssl rsa -passin pass:1111 -in server.key -out server.key

echo Generate client key
openssl genrsa -passout pass:1111 -des3 -out client.key 4096

echo Generate client signing request:
openssl req -passin pass:1111 -new -key client.key -out client.csr -subj  "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=client"

echo Self-sign client certificate:
openssl x509 -passin pass:1111 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt

echo Remove passphrase from client key:
openssl rsa -passin pass:1111 -in client.key -out client.key
pause
  • 基于SSL的服务端启动如下,创建服务的时候请使用主机名(开发环境)或域名(生产环境),不要使用IP地址。
 public static void RpcServerSsl()
{
    var cacert = File.ReadAllText(CombinePath("ca.crt"));
    var servercert = File.ReadAllText(CombinePath("server.crt"));
    var serverkey = File.ReadAllText(CombinePath("server.key"));
    var keypair = new KeyCertificatePair(servercert, serverkey);
    var sslCredentials = new SslServerCredentials(new List<KeyCertificatePair>() { keypair }, cacert, false);

    var server = new Server
    {
        Services = { MathService.BindService(new MathServiceImpl()) },
        Ports = { new ServerPort("KEKYK", sslPort, sslCredentials) }
    };
    server.Start();
    server.Ports.ToList().ForEach(a => Console.WriteLine($"Server (SSL) listening on port {a.Port}..."));
    Console.ReadLine();
}
  • 基于SSL的客户端使用如下,注意测试环境中使用主机名,生产环境中使用域名来,不要使用任何形式的IP地址。
public static void RpcClientSsl()
{
    var cacert = File.ReadAllText(CombinePath("ca.crt"));
    var clientcert = File.ReadAllText(CombinePath("client.crt"));
    var clientkey = File.ReadAllText(CombinePath("client.key"));
    var ssl = new SslCredentials(cacert, new KeyCertificatePair(clientcert, clientkey));
    var channel = new Channel("KEKYK", sslPort, ssl);
    var client = new MathService.MathServiceClient(channel);

    var random = new Random();
    while (true)
    {
        var first = random.NextDouble();
        var second = random.NextDouble();

        var reply = client.AddAsync(new AddRequest { First = first, Second = second }, new CallOptions()).ResponseAsync.Result;
        Console.WriteLine($"RPC call Add service: {first:F4} + {second:F4} = {reply.Sum:F4}");
        Thread.Sleep(1000);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容