Skip to main content

gRPC 高效通訊指南

用途:高效能、強型別的跨服務 RPC 通訊框架(HTTP/2 + Protocol Buffers)

NuGetGrpc.Net.Client(客戶端)/ Grpc.AspNetCore(伺服端)

授權:Apache-2.0

支援:.NET 6+

GitHubgrpc/grpc-dotnet


什麼是 gRPC

gRPC(gRPC Remote Procedure Call)是 Google 開發的高效能 RPC 框架。它用 Protocol Buffers(protobuf) 做二進制序列化,透過 HTTP/2 傳輸,效能遠優於傳統的 JSON over HTTP。

在工業自動化場景中,設備資料量大(多台 PLC 同時上報感測器數據)、延遲要求低(控制命令即時送達)、型別安全很重要(暫存器值的型別不能搞錯)。gRPC 在這些方面都有明顯優勢。

核心特性

特性說明
Protocol Buffers二進制序列化,比 JSON 小 3-10 倍,解析快 10 倍
HTTP/2多路複用、Header 壓縮、Server Push
強型別.proto 檔案自動生成型別安全的 Client/Server 程式碼
串流支援原生支援 Server/Client/雙向串流
多語言同一份 .proto 可生成 C#、Python、Go、Java 等程式碼

vs REST API vs SignalR vs ZeroMQ

比較項目gRPCREST (JSON/HTTP)SignalRZeroMQ
序列化Protobuf(二進制)JSON(文字)JSON / MessagePack自訂
傳輸HTTP/2HTTP/1.1 or 2WebSocketTCP/IPC
型別安全✅ 編譯期❌ 執行期❌ 執行期❌ 無
串流✅ 原生四種模式❌ 需要 SSE/WebSocket✅ 即時雙向✅ Pub/Sub
效能極高中等極高
瀏覽器支援❌ 需 gRPC-Web✅ 原生✅ 原生❌ 無
學習曲線中等(需學 proto)
適合場景服務間通訊、設備數據串流公開 API、Web 前端即時 UI 更新超低延遲
公司場景選擇
  • 設備資料即時串流(PLC → 監控端):gRPC Server Streaming
  • 服務間通訊(MES 整合層 ↔ 設備控制層):gRPC Unary
  • WPF UI 即時更新:SignalR 或直接用 Rx.NET
  • 公開 REST API(給外部系統呼叫):仍用 REST

四種通訊模式

gRPC 支援四種模式,涵蓋幾乎所有通訊場景:

1. Unary(一問一答)

最常見的模式,等同於傳統的 Request/Response:

場景:發送控制命令(啟動/停止設備)、查詢設備狀態

2. Server Streaming(伺服器推流)

客戶端發一個請求,伺服器持續推送多筆回應,直到客戶端取消或伺服器主動結束:

場景:訂閱 PLC 感測器數據、即時監控資料推送

3. Client Streaming(客戶端推流)

客戶端持續送出多筆資料,伺服器最後回一個彙總回應:

場景:批次上傳感測器歷史資料、檔案上傳

4. Bidirectional Streaming(雙向推流)

雙方在同一條連線上同時持續推送,互不依賴對方節奏:

場景:多設備即時命令與回報、雙向互動控制


.proto 定義

gRPC 的介面用 .proto 檔案定義。這份檔案同時描述服務介面和資料結構,然後自動生成 C# 程式碼。

設備服務範例

syntax = "proto3";

option csharp_namespace = "GatherTech.Equipment.Grpc";

package equipment;

// 設備控制服務
service DeviceService {
// 一問一答:查詢設備狀態
rpc GetStatus (DeviceRequest) returns (DeviceStatus);

// 一問一答:發送控制命令
rpc SendCommand (DeviceCommand) returns (CommandResult);

// Server Streaming:訂閱感測器數據
rpc StreamSensorData (SensorSubscription) returns (stream SensorData);

// Bidirectional:雙向控制通道
rpc ControlChannel (stream DeviceCommand) returns (stream DeviceEvent);
}

message DeviceRequest {
string device_id = 1;
}

message DeviceStatus {
string device_id = 1;
bool is_connected = 2;
string mode = 3; // "Auto", "Manual", "Maintenance"
double temperature = 4;
double pressure = 5;
int64 uptime_seconds = 6;
}

message DeviceCommand {
string device_id = 1;
string command = 2; // "Start", "Stop", "Reset"
map<string, string> parameters = 3;
}

message CommandResult {
bool success = 1;
string message = 2;
}

message SensorSubscription {
string device_id = 1;
int32 interval_ms = 2; // 推送間隔(毫秒)
}

message SensorData {
string device_id = 1;
int64 timestamp = 2; // Unix timestamp (ms)
double temperature = 3;
double pressure = 4;
double flow_rate = 5;
int32 status_code = 6;
}

message DeviceEvent {
string device_id = 1;
string event_type = 2; // "AlarmRaised", "StateChanged", "CommandAck"
string payload = 3;
int64 timestamp = 4;
}

安裝

伺服端(ASP.NET Core)

dotnet add package Grpc.AspNetCore

客戶端

dotnet add package Grpc.Net.Client
dotnet add package Google.Protobuf
dotnet add package Grpc.Tools # .proto → C# 自動生成

專案檔設定(.csproj)

<ItemGroup>
<Protobuf Include="Protos\device.proto" GrpcServices="Both" />
</ItemGroup>

GrpcServices 可以是 ServerClientBoth,控制只生成伺服端、客戶端或兩者的程式碼。


基本範例:設備狀態查詢

伺服端實作

public class DeviceServiceImpl : DeviceService.DeviceServiceBase
{
private readonly IDeviceManager _deviceManager;
private readonly ILogger<DeviceServiceImpl> _logger;

public DeviceServiceImpl(
IDeviceManager deviceManager,
ILogger<DeviceServiceImpl> logger)
{
_deviceManager = deviceManager;
_logger = logger;
}

public override async Task<DeviceStatus> GetStatus(
DeviceRequest request, ServerCallContext context)
{
_logger.LogDebug("GetStatus request for {DeviceId}", request.DeviceId);

var device = await _deviceManager.GetDeviceAsync(request.DeviceId);

return new DeviceStatus
{
DeviceId = device.Id,
IsConnected = device.IsConnected,
Mode = device.Mode.ToString(),
Temperature = device.Temperature,
Pressure = device.Pressure,
UptimeSeconds = (long)device.Uptime.TotalSeconds
};
}
}

// 註冊服務(Program.cs)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();

var app = builder.Build();
app.MapGrpcService<DeviceServiceImpl>();
app.Run();

客戶端呼叫

// 建立連線
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new DeviceService.DeviceServiceClient(channel);

// Unary 呼叫
var status = await client.GetStatusAsync(new DeviceRequest
{
DeviceId = "CMP-01"
});

Console.WriteLine($"Device {status.DeviceId}: " +
$"Connected={status.IsConnected}, " +
$"Temp={status.Temperature}°C");

延伸閱讀