Skip to main content
0108🏡

一月以来,原先的MCVV已经走到末尾了,已经慢慢接手新的工作了。这里简要概括一下:次世代的活是SBEBackEnd的相关代码,现在需要将部分API中的调别的API的方式由“Rest请求”改为“Grpc呼出”。

是不是看起来不难,下面简要概述一下修改经过。

  • 第一版🤣:既然你说要修改,那就找到这个API的代码中发送Rest Http请求的地方,然后直接注掉,原来是什么参数,现在还是什么参数(re),打包扔给Grpc。这种方法在每一个API中都改写了一遍🤣,可能此时你已经发现了,调grpc的时候都是相同的逻辑,其实是可以抽出来的,不然的话可能就会建立很多个连接到服务器。
string url = ConfigManager.GetAppConfig()[SmartphoneBEConstants.ModuleName.SmartphoneBE.ToString() + SmartphoneBEConstants.APPJSON_SEPARATER + GRPCSERVICEURL];
var grpcService = GrpcChannel.ForAddress(url);
var client = new Messenger0304.Messenger0304Client(grpcService);

// Serviceサーバー呼び出す
Reply reply = await client.CommunicateAsync(re);

// GrpcService接続閉じる
await grpcService.ShutdownAsync().ConfigureAwait(false);
  • 第二版😆:由于第一版的原因,第二版开始找人写共通了,具体来说是写了一个静态类,将调grpc的封装好,供其他API直接调用这个方法(如下)。其他API只要传入服务的地址和自己请求的参数即可。但是还是有缺点。1:可以看到是没有任何出log的语句的,出错不好排查;2:并没有进行try catch,无法获取详细异常信息。
public static async Task<string> GRPCConnectionMethodsAsync(string url, Request request, [NotNull] LogManager log)
{
// GrpcServiceサーバー設定
var grpcService = GrpcChannel.ForAddress(url);
var client = new Messenger.MessengerClient(grpcService);

// Serviceサーバー呼び出す
Reply reply = await client.CommunicateAsync(request).ConfigureAwait(false);

// GrpcService接続閉じる
_ = grpcService.ShutdownAsync().ConfigureAwait(false);

return reply.Message;
}
  • 第三版🙋:增加try catch和写log。至此,共通部分算是基本完成了。
public static async Task<string> GRPCConnectionMethodsAsync(string url, Request request, [NotNull] LogManager log)
{
if (url == null)
{
throw new ArgumentNullException("url");
}

if (request == null)
{
throw new ArgumentNullException("request");
}

// Grpc実行前のログ
log.Info(() =>
log.Info(
string.Format(
"Grpc Request\nURL:(\n{0})\nappId:{1}\nacceptDate:{2}\nuserId:{3}\nvin:{4}\ninternalVin:{5}\nrequestDate:{6}\ncontents:{7}\nreqSeq:{8}\noptionalData:{9}",
url,
request.AppId,
request.AcceptDate,
request.UserId,
request.Vin,
request.InternalVin,
request.RequestDate,
request.Contents.ToStringUtf8(),
request.ReqSeq,
request.OptionalData),
request.TransactionId,
"Grpc Service",
"ControllersUtil"));

// GrpcServiceサーバー設定
var grpcService = GrpcChannel.ForAddress(url);
Reply reply = new Reply();
try
{
var client = new Messenger.MessengerClient(grpcService);

// Serviceサーバー呼び出す
reply = await client.CommunicateAsync(request).ConfigureAwait(false);
}
catch (Exception ex) when (ex is ArgumentException || ex is Exception)
{
log.Error(() =>
log.Error(
string.Format(
Resources.MessageCode_490S02_AKSMSG,
ex.Message,
ex.StackTrace,
ex.HResult,
ex.InnerException),
request.TransactionId,
"Grpc Service",
"ControllersUtil",
SmartphoneBEConstants.ERRORCODE_490S02));
}
finally
{
// GrpcService接続閉じる
_ = grpcService.ShutdownAsync().ConfigureAwait(false);
}

// Grpc実行後のログ
log.Info(() =>
log.Info(
string.Format(
"Grpc ResponseBody\nmessage:(\n{0})",
reply.Message),
request.TransactionId,
"Grpc Service",
"ControllersUtil"));

return reply.Message;
}
  • 现在来看看别的API都是怎么调这个方法的:整理好requestData和url,就可以了。
Request request = new Request
{
AppId = this.AppId,
TransactionId = transactionId,
AcceptDate = this.AcceptDate,
UserId = Convert.ToString(internalUserId),
Vin = vin,
InternalVin = Convert.ToString(internalVin),
RequestDate = requestDate,
Contents = ByteString.CopyFromUtf8(contents),
ReqSeq = 0,
OptionalData = ByteString.CopyFromUtf8(string.Empty),
};

try
{
// gRPCに経由でAPIを呼び出す
string responseJsonData = await ControllersUtil.GRPCConnection.GRPCConnectionMethodsAsync(gRPCUrl, request, this.LogManager).ConfigureAwait(false);
}


扩展阅读
  • Grpc(gRPC Remote Procedure Call):远程过程调用(RPC)框架。通过编写Protobuf 文件,来定义我们的服务和消息。然后通过google的Protobuf 编译器将定义的 Protobuf 文件编译成各个编程语言的类。
syntax = "proto3";

package example;

// 定义 Greeter 服务
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}

// 定义请求消息 HelloRequest
message HelloRequest {
string name = 1;
}

// 定义响应消息 HelloReply
message HelloReply {
string message = 1;
}
  • 服务实现:其中的Greeter和GreeterBase就是通过Protobuf 编译器将定义的 Protobuf 文件编译而生成的类。
using System.Threading.Tasks;
using Grpc.Core;

namespace Example
{
// 实现 Greeter 服务
public class GreeterService : Greeter.GreeterBase
{
// 实现 SayHello 方法
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
// 构造响应消息
string message = $"Hello, {request.Name}!";
return Task.FromResult(new HelloReply { Message = message });
}
}
}
  • 服务器端实现:
using Grpc.Core;

class Program
{
const int Port = 50051;

static void Main()
{
// 创建 gRPC 服务器
Server server = new Server
{
// 注册 Greeter 服务
Services = { Greeter.BindService(new GreeterService()) },
// 指定监听地址和端口
Ports = { new ServerPort("localhost", Port, ServerCredentials.Insecure) }
};
// 启动服务器
server.Start();

Console.WriteLine($"Server listening on port {Port}");
Console.WriteLine("Press any key to stop the server...");
Console.ReadKey();

// 关闭服务器
server.ShutdownAsync().Wait();
}
}
  • 客户端实现:
using Grpc.Core;

class Program
{
static void Main()
{
// 创建 gRPC 客户端通道
Channel channel = new Channel("localhost:50051", ChannelCredentials.Insecure);
// 创建 Greeter 客户端
var client = new Greeter.GreeterClient(channel);

// 创建请求消息
var request = new HelloRequest { Name = "John" };
// 调用远程服务
var reply = client.SayHello(request);

// 打印服务器的响应消息
Console.WriteLine($"Server replied: {reply.Message}");

// 关闭通道
channel.ShutdownAsync().Wait();
}
}
  • 再补充一下c#中的异步编程的一点浅显了解
internal class Program
{
static void Main(string[] args)
{
DownloadHandle();
Console.ReadLine();
}

public static async void DownloadHandle()
{
Console.WriteLine("downloadStart!-> MainThreadID:" + Thread.CurrentThread.ManagedThreadId);
await Download();
//Download();
Console.WriteLine("downloadCompleted!-> MainThreadID:" + Thread.CurrentThread.ManagedThreadId);
}

public static Task Download()
{
return Task.Run(() =>
{
Console.WriteLine("DownloadThreadID:-> " + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("10%");
Console.WriteLine("30%");
Console.WriteLine("50%");
Console.WriteLine("60%");
Console.WriteLine("80%");
Console.WriteLine("99%");
Console.WriteLine("100%");
});
//Console.WriteLine("下载线程ID:->" + Thread.CurrentThread.ManagedThreadId);
//Console.WriteLine("10%");
//Console.WriteLine("30%");
//Console.WriteLine("50%");
//Console.WriteLine("60%");
//Console.WriteLine("80%");
//Console.WriteLine("99%");
//Console.WriteLine("100%");
//return Task.CompletedTask;
}
}

在上面的DownloadHandle中以及下面的Download中,有注释部分,组合起来有4种情况:

  1. await Download 和 Task.Run

  2. await Download 和 return Task.CompletedTask

  3. Download 和 Task.Run

  4. Download 和 return Task.CompletedTask

  • 首先是第一种情况:使用 async/await 的情况,DownloadHandle在一个线程执行,Download会异步执行,并且分配给另一个线程,直到DownloadHandle等待它执行完毕。

  • 第二种情况:DownloadHandle中使用 async/await,DownloadHandle在一个线程执行,然而Download中并不是Task.Run,所以它也会被分配给DownloadHandle所在的线程,然后顺序执行。

  • 第三种情况:DownloadHandle不使用 async/await,但是Download是Task.Run,DownloadHandle在一个线程执行,Download会异步执行,并且分配给另一个线程,但是主线程并不会等待Download的执行,并且两个线程的执行是系统分配调度的,无法预知代码执行顺序了。

  • 第四种情况:不适用async/await和Task.Run,全部是在同一线程上执行的,就按顺序走。