GRPC 是Google發(fā)布的一個(gè)開(kāi)源、高性能、通用RPC(Remote Procedure Call)框架阵赠。提供跨語(yǔ)言、跨平臺(tái)支持肌稻。以下以一個(gè).NET Core Console項(xiàng)目演示如何使用GRPC框架清蚀。
一、定義服務(wù)
通過(guò)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)
通過(guò)以下批處理命令generate_protos.bat
將服務(wù)定義生成多種語(yǔ)言和平臺(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語(yǔ)言东揣,protoc.exe編譯器生成了如下圖幾個(gè)類,其中左邊4個(gè)類用于構(gòu)造請(qǐng)求和響應(yīng)對(duì)象腹泌,MathService類用于下一步構(gòu)造服務(wù)和消費(fèi)服務(wù)嘶卧。
三、實(shí)現(xiàn)并運(yùn)行服務(wù)
通過(guò)上一步的編譯凉袱,自動(dòng)生成了MathService類芥吟,下面通過(guò)該類構(gòu)造并啟動(dòng)grpc服務(wù)。
通過(guò)繼承基類實(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ù)
客戶端通過(guò)創(chuàng)建一個(gè)Channel和一個(gè)服務(wù)客戶端來(lái)使用服務(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);
}
五涤躲、使用SSL實(shí)現(xiàn)加密通訊
grpc默認(rèn)實(shí)現(xiàn)了基于證書(shū)的SSL加密通訊棺耍,使用中需要注意以下事項(xiàng)。
在Windows上開(kāi)發(fā)請(qǐng)安裝 OpenSSL對(duì)應(yīng)版本并將openssl.exe所在路徑添加到環(huán)境變量中种樱。
-
通過(guò)以下樣例腳本生成通訊中所需要的服務(wù)端和客戶端證書(shū)蒙袍,其中需要特別注意的是俊卤,Generate server signing request:中的CN=KEKYK字段如果是本機(jī)測(cè)試,請(qǐng)一定使用本機(jī)名稱左敌,如果是真實(shí)環(huán)境請(qǐng)使用域名瘾蛋,因?yàn)榭蛻舳吮仨毻ㄟ^(guò)機(jī)器名(本地測(cè)試)或域名訪問(wèn)該服務(wù)。如果此處CN字段不使用機(jī)器名或域名矫限,將導(dǎo)致以下錯(cuò)誤:
生成服務(wù)端和客戶端證書(shū)腳本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ī)名(開(kāi)發(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的客戶端使用如下取董,注意測(cè)試環(huán)境中使用主機(jī)名,生產(chǎn)環(huán)境中使用域名來(lái)无宿,不要使用任何形式的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);
}
}