اتصال interprocess باستخدام GRPC

أود اليوم أن أتحدث عن طريقتنا في تنفيذ الاتصال بين العمليات بين التطبيقات على NET Core و NET Framework باستخدام بروتوكول GRPC. المفارقة هي أن GRPC ، التي روجت لها Microsoft كبديل لـ WCF على منصات NET Core و NET5 ، حدث في حالتنا على وجه التحديد بسبب التنفيذ غير الكامل لـ WCF في NET Core.



آمل أن يتم العثور على هذه المقالة عندما يفكر شخص ما في خيارات تنظيم IPC ويسمح لك بالنظر في مثل هذا الحل عالي المستوى مثل GRPC من هذا الجانب منخفض المستوى.



ارتبط نشاط عملي بأكثر من 7 سنوات بما يسمى "المعلوماتية الصحية". هذه منطقة مثيرة للاهتمام إلى حد ما ، على الرغم من أن لها خصائصها الخاصة. بعضها يمثل الكم الهائل من التقنيات القديمة (المحافظة) وقربًا معينًا من التكامل في معظم الحلول الحالية (تأمين البائع على النظام البيئي لمصنع واحد).



سياق الكلام



لقد واجهنا مزيجًا من هاتين الميزتين في المشروع الحالي: كنا بحاجة إلى بدء العمل وتلقي البيانات من مجمع معين للبرامج والأجهزة. في البداية ، بدا كل شيء جيدًا جدًا: يعرض جزء البرنامج في المجمع خدمة WCF ، والتي تقبل أوامر التنفيذ وتبصق النتائج في ملف. علاوة على ذلك ، توفر الشركة المصنعة SDK مع أمثلة! ما الخطأ الذي يمكن أن يحدث؟ كل شيء تكنولوجي وحديث تمامًا. لا ASTM بعصي مقسمة ، ولا حتى مشاركة الملفات عبر مجلد مشترك.



ولكن لسبب غريب ، تستخدم خدمة WCF القنوات المزدوجة والربط WSDualHttpBinding، والتي لا تتوفر ضمن .NET Core 3.1 ، فقط في إطار العمل "الكبير" (أو بالفعل في "القديم"؟). في هذه الحالة ، لا يتم استخدام ازدواجية القنوات بأي شكل من الأشكال! إنه فقط في وصف الخدمة. المشكله! بعد كل شيء ، ما تبقى من المشروع يعيش على NET Core وليس هناك رغبة في رفضه. سيتعين علينا جمع "برنامج التشغيل" هذا كتطبيق منفصل في NET Framework 4.8 ومحاولة تنظيم تدفق البيانات بين العمليات بطريقة ما.



اتصال interprocess



. , , , , tcp-, - RPC . IPC:



  • ,
  • Windows ( 7 )
  • NET Framework NET Core


, , . ?





, . , . , "". , — . , . , "" "". ? , : , , .



. . , , , workaround, . .



GRPC



, , . GRPC. GRPC? , . .



, :



  • , — , Unary call
  • — , server streaming rpc
  • — HTTP/2
  • Windows ( 7 ) — ,
  • NET Framework NET Core —
  • — , protobuf
  • ,


GRPC 5



tutorial. GRPC , , IPC - . .





:



  • IpcGrpcSample.CoreClient — NET Core 3.1, RPC
  • IpcGrpcSample.NetServer — NET Framework 4.8, RPC
  • IpcGrpcSample.Protocol — , NET Standard 2.0. RPC


NET Framework Properties\AssemblyInfo.cs



<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
  </PropertyGroup>
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>...</PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">...</PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">...</PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <None Include="App.config" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>


NuGet!



  • IpcGrpcSample.Protocol Google.Protobuf, Grpc Grpc.Tools
  • Grpc, Grpc.Core, Microsoft.Extensions.Hosting Microsoft.Extensions.Hosting.WindowsServices.
  • Grpc.Net.Client OneOf — .


gRPC



GreeterService? - . . -, .



.proto IpcGrpcSample.Protocol. Protobuf- .





//  
syntax = "proto3"; 
//   Empty
import "google/protobuf/empty.proto";
//       
option csharp_namespace = "IpcGrpcSample.Protocol.Extractor"; 
//   RPC   
service ExtractorRpcService {  
  //   "" 
  rpc Start (google.protobuf.Empty) returns (StartResponse);  
}

//   
message StartResponse {
    bool Success = 1;
}




//  
syntax = "proto3"; 
//       
option csharp_namespace = "IpcGrpcSample.Protocol.Thermocycler"; 
//   RPC   
service ThermocyclerRpcService {  
  // server-streaming  " ".      -,    
  rpc Start (StartRequest) returns (stream StartResponse);  
}

//   -    
message StartRequest {
  //   -     
  string ExperimentName = 1;
  //    -  ,      " "   
  int32 CycleCount = 2;
}

//     
message StartResponse {
  //  
  int32 CycleNumber = 1;
  //     oneof -       . 
  // -  discriminated union,  
  oneof Content {
    //    
    PlateRead plate = 2;
    //   
    StatusMessage status = 3;
  }
}

message PlateRead {
  string ExperimentalData = 1;
}

message StatusMessage {
  int32 PlateTemperature = 2;
}


proto- protobuf . csproj :



  <ItemGroup>
    <Protobuf Include="**\*.proto" />
  </ItemGroup>




2020 Hosting NET Core. Program.cs:



class Program
{
    static Task Main(string[] args) => CreateHostBuilder(args).Build().RunAsync();

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
        .UseWindowsService()
        .ConfigureServices(services =>
        {
            services.AddLogging(loggingBuilder =>
            {
                loggingBuilder.ClearProviders();
                loggingBuilder.SetMinimumLevel(LogLevel.Trace);                    
                loggingBuilder.AddConsole();
            });
            services.AddTransient<ExtractorServiceImpl>(); //   -     
            services.AddTransient<ThermocyclerServiceImpl>();
            services.AddHostedService<GrpcServer>(); //  GRPC   HostedService
        });
}


. () .



— , — . TLS ( ) — ServerCredentials.Insecure. http/2 — .



internal class GrpcServer : IHostedService
{
    private readonly ILogger<GrpcServer> logger;
    private readonly Server server;
    private readonly ExtractorServiceImpl extractorService;
    private readonly ThermocyclerServiceImpl thermocyclerService;

    public GrpcServer(ExtractorServiceImpl extractorService, ThermocyclerServiceImpl thermocyclerService, ILogger<GrpcServer> logger)
    {
        this.logger = logger;
        this.extractorService = extractorService;
        this.thermocyclerService = thermocyclerService;
        var credentials = BuildSSLCredentials(); //       . 
        server = new Server //  
        {
            Ports = { new ServerPort("localhost", 7001, credentials) }, //      
            Services = //       
            {
                ExtractorRpcService.BindService(this.extractorService),
                ThermocyclerRpcService.BindService(this.thermocyclerService)
            }
        };            
    }

    /// <summary>
    ///       
    /// </summary>
    private ServerCredentials BuildSSLCredentials()
    {
        var cert = File.ReadAllText("cert\\server.crt");
        var key = File.ReadAllText("cert\\server.key");

        var keyCertPair = new KeyCertificatePair(cert, key);
        return new SslServerCredentials(new[] { keyCertPair });
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation(" GRPC ");
        server.Start();
        logger.LogInformation("GRPC  ");
        return Task.CompletedTask;
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation(" GRPC ");
        await server.ShutdownAsync();
        logger.LogInformation("GRPC  ");
    }
}


!

. :



internal class ExtractorServiceImpl : ExtractorRpcService.ExtractorRpcServiceBase
{
    private static bool success = true;
    public override Task<StartResponse> Start(Empty request, ServerCallContext context)
    {
        success = !success;
        return Task.FromResult(new StartResponse { Success = success });
    }
}


- :



internal class ThermocyclerServiceImpl : ThermocyclerRpcService.ThermocyclerRpcServiceBase
{
    private readonly ILogger<ThermocyclerServiceImpl> logger;

    public ThermocyclerServiceImpl(ILogger<ThermocyclerServiceImpl> logger)
    {
        this.logger = logger;
    }

    public override async Task Start(StartRequest request, IServerStreamWriter<StartResponse> responseStream, ServerCallContext context)
    {
        logger.LogInformation(" ");
        var rand = new Random(42);
        for(int i = 1; i <= request.CycleCount; ++i)
        {
            logger.LogInformation($"  {i}");
            var plate = new PlateRead { ExperimentalData = $" {request.ExperimentName},  {i}  {request.CycleCount}: {rand.Next(100, 500000)}" };
            await responseStream.WriteAsync(new StartResponse { CycleNumber = i, Plate = plate });
            var status = new StatusMessage { PlateTemperature = rand.Next(25, 95) };
            await responseStream.WriteAsync(new StartResponse { CycleNumber = i, Status = status });
            await Task.Delay(500);
        }
        logger.LogInformation(" ");
    }
}


. GRPC Ctrl-C:



dbug: Microsoft.Extensions.Hosting.Internal.Host[1]
      Hosting starting
info: IpcGrpcSample.NetServer.GrpcServer[0]
       GRPC 
info: IpcGrpcSample.NetServer.GrpcServer[0]
      GRPC  
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\Users\user\source\repos\IpcGrpcSample\IpcGrpcSample.NetServer\bin\Debug
dbug: Microsoft.Extensions.Hosting.Internal.Host[2]
      Hosting started
info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...
dbug: Microsoft.Extensions.Hosting.Internal.Host[3]
      Hosting stopping
info: IpcGrpcSample.NetServer.GrpcServer[0]
       GRPC 
info: IpcGrpcSample.NetServer.GrpcServer[0]
      GRPC  
dbug: Microsoft.Extensions.Hosting.Internal.Host[4]
      Hosting stopped


: NET Framework, WCF etc. Kestrel!



grpcurl, . NET Core.



NET Core



. .



. gRPC . RPC .



class ExtractorClient
{
    private readonly ExtractorRpcService.ExtractorRpcServiceClient client;

    public ExtractorClient()
    {
        //AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); //      http/2  TLS        
        var httpClientHandler = new HttpClientHandler
        {
            ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator //    
        };
        var httpClient = new HttpClient(httpClientHandler);
        var channel = GrpcChannel.ForAddress("https://localhost:7001", new GrpcChannelOptions { HttpClient = httpClient });
        client = new ExtractorRpcService.ExtractorRpcServiceClient(channel);
    }

    public async Task<bool> StartAsync()
    {
        var response = await client.StartAsync(new Empty());
        return response.Success;
    }
}


IAsyncEnumerable<> OneOf<,> — .



public async IAsyncEnumerable<OneOf<string, int>> StartAsync(string experimentName, int cycleCount)
{
    var request = new StartRequest { ExperimentName = experimentName, CycleCount = cycleCount };
    using var call = client.Start(request, new CallOptions().WithDeadline(DateTime.MaxValue)); //   
    while (await call.ResponseStream.MoveNext())
    {
        var message = call.ResponseStream.Current;
        switch (message.ContentCase)
        {
            case StartResponse.ContentOneofCase.Plate:
                yield return message.Plate.ExperimentalData;
                break;
            case StartResponse.ContentOneofCase.Status:
                yield return message.Status.PlateTemperature;
                break;
            default:
                break;
        };
    }
}


.



HTTP/2 Windows 7



, Windows TLS HTTP/2. , :



server = new Server //  
{
    Ports = { new ServerPort("localhost", 7001, ServerCredentials.Insecure) }, //      
    Services = //       
    {
        ExtractorRpcService.BindService(this.extractorService),
        ThermocyclerRpcService.BindService(this.thermocyclerService)
    }
};            


http, https. . , http/2:



AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);


تم إجراء العديد من التبسيط في رمز المشروع عن قصد - لا يتم التعامل مع الاستثناءات ، ولا يتم إجراء التسجيل بشكل طبيعي ، ويتم ترميز المعلمات في الكود. هذا ليس جاهزًا للإنتاج ، ولكنه نموذج لحل المشكلات. آمل أن يكون ممتعًا ، اطرح الأسئلة!




All Articles