.NET Core 使用 grpc 實(shí)現(xiàn)微服務(wù)

GRPC 是Google發(fā)布的一個(gè)開源、高性能、通用RPC(Remote Procedure Call)框架。提供跨語言、跨平臺(tái)支持。以下以一個(gè).NET Core Console項(xiàng)目演示如何使用GRPC框架。

一、定義服務(wù)

通過proto定義一個(gè)數(shù)學(xué)計(jì)算服務(wù),其中包括兩個(gè)服務(wù)方法(Add, Multipy)以及4個(gè)請(qǐng)求響應(yīng)對(duì)象(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;

// 數(shù)學(xué)運(yùn)算服務(wù)
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;
}

二、將服務(wù)編譯成存根(stub)

通過以下批處理命令generate_protos.bat將服務(wù)定義生成多種語言和平臺(tái)版本的客戶端和服務(wù)端存根。

@rem 生成客戶端和服務(wù)器端存根

setlocal

@rem 進(jìn)入當(dāng)前目錄
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

針對(duì)CSHARP語言,protoc.exe編譯器生成了如下圖幾個(gè)類,其中左邊4個(gè)類用于構(gòu)造請(qǐng)求和響應(yīng)對(duì)象,MathService類用于下一步構(gòu)造服務(wù)和消費(fèi)服務(wù)。


CSHARP STUBS

三、實(shí)現(xiàn)并運(yùn)行服務(wù)

通過上一步的編譯,自動(dòng)生成了MathService類,下面通過該類構(gòu)造并啟動(dòng)grpc服務(wù)。

通過繼承基類實(shí)現(xiàn)服務(wù)接口

    /// <summary>
    /// 實(shí)現(xiàn)RPC服務(wù)端接口。
    /// </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 });
        }
    }

啟動(dòng)服務(wù)

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();

四、客戶端調(diào)用服務(wù)

客戶端通過創(chuàng)建一個(gè)Channel和一個(gè)服務(wù)客戶端來使用服務(wù)。

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調(diào)用

五、使用SSL實(shí)現(xiàn)加密通訊

grpc默認(rèn)實(shí)現(xiàn)了基于證書的SSL加密通訊,使用中需要注意以下事項(xiàng)。

  • 在Windows上開發(fā)請(qǐng)安裝 OpenSSL對(duì)應(yīng)版本并將openssl.exe所在路徑添加到環(huán)境變量中。

  • 通過以下樣例腳本生成通訊中所需要的服務(wù)端和客戶端證書,其中需要特別注意的是,Generate server signing request:中的CN=KEKYK字段如果是本機(jī)測試,請(qǐng)一定使用本機(jī)名稱,如果是真實(shí)環(huán)境請(qǐng)使用域名,因?yàn)榭蛻舳吮仨毻ㄟ^機(jī)器名(本地測試)或域名訪問該服務(wù)。如果此處CN字段不使用機(jī)器名或域名,將導(dǎo)致以下錯(cuò)誤:


    CN字段不使用主機(jī)名或域名時(shí)產(chǎn)生的錯(cuò)誤
  • 生成服務(wù)端和客戶端證書腳本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的服務(wù)端啟動(dòng)如下,創(chuàng)建服務(wù)的時(shí)候請(qǐng)使用主機(jī)名(開發(fā)環(huán)境)或域名(生產(chǎn)環(huán)境),不要使用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的客戶端使用如下,注意測試環(huán)境中使用主機(jī)名,生產(chǎn)環(huán)境中使用域名來,不要使用任何形式的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);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容