一元与服务器流式 RPC
实现一元调用和服务器端流式传输,让服务器实时向客户端推送数据。
一元与服务器流式 RPC 是 CoddyKit 上的免费 C# Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。
gRPC RPC 类型
gRPC 支持四种 RPC 模式:一元(请求/响应)、服务器流式传输(客户端发送一个请求,服务器发送多个响应)、客户端流式传输和双向流式传输。本课介绍前两种模式。
一元 RPC:基本模式
一元 RPC 是最简单的模式——一个请求,一个响应。它看起来像普通的函数调用,但会通过 HTTP/2 传输,并使用 Protobuf 编码。
// .proto definition
service OrderService {
rpc GetOrder (GetOrderRequest) returns (OrderResponse);
}
message GetOrderRequest { int32 id = 1; }
message OrderResponse { int32 id = 1; string status = 2; double total = 3; }实现一元 RPC
重写生成的基类方法。ServerCallContext 提供元数据、取消令牌、截止时间和对等方信息。
public class OrderService : OrderService.OrderServiceBase
{
private readonly IOrderRepository _repo;
public OrderService(IOrderRepository repo) => _repo = repo;
public override async Task<OrderResponse> GetOrder(
GetOrderRequest request,
ServerCallContext context)
{
var order = await _repo.GetByIdAsync(request.Id, context.CancellationToken);
if (order is null)
throw new RpcException(new Status(StatusCode.NotFound, "Order not found"));
return new OrderResponse
{
Id = order.Id,
Status = order.Status.ToString(),
Total = (double)order.Total
};
}
}从 Client 调用一元 RPC
生成的 client 同时提供看起来同步的方法和真正的 async 方法。在生产环境中始终使用 Async 变体。
var client = new OrderService.OrderServiceClient(channel);
// Async call with cancellation
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var reply = await client.GetOrderAsync(
new GetOrderRequest { Id = 42 },
cancellationToken: cts.Token);
Console.WriteLine($"Order {reply.Id}: {reply.Status} - ${reply.Total}");服务端流式传输:Proto 定义
服务端流式传输会在响应类型前添加 stream 关键字。服务器会在关闭流之前,通过同一连接发送多条消息。
service StockService {
// Client sends one symbol, server streams price updates
rpc WatchStock (WatchRequest) returns (stream StockUpdate);
}
message WatchRequest { string symbol = 1; }
message StockUpdate { string symbol = 1; double price = 2; int64 timestamp = 3; }实现服务端流式传输
使用 IServerStreamWriter<T> 发送消息。该方法返回一个 Task:持续写入,完成后返回。
public override async Task WatchStock(
WatchRequest request,
IServerStreamWriter<StockUpdate> responseStream,
ServerCallContext context)
{
while (!context.CancellationToken.IsCancellationRequested)
{
var price = await _market.GetPriceAsync(request.Symbol);
await responseStream.WriteAsync(new StockUpdate
{
Symbol = request.Symbol,
Price = price,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
});
await Task.Delay(1000, context.CancellationToken);
}
}从 Client 消费服务端流
对 ResponseStream.ReadAllAsync() 使用 await foreach,以便在每条消息到达时进行处理。
using var call = client.WatchStock(
new WatchRequest { Symbol = "MSFT" });
await foreach (var update in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine($"{update.Symbol}: ${update.Price:F2}");
}
// Continues until server closes the stream or client cancels取消服务端流
打开 call 时传入 CancellationToken,即可在客户端停止流。服务器会通过自己的 token 接收取消信号。
var cts = new CancellationTokenSource();
// Cancel after 30 seconds
cts.CancelAfter(TimeSpan.FromSeconds(30));
using var call = client.WatchStock(
new WatchRequest { Symbol = "AAPL" },
cancellationToken: cts.Token);
try
{
await foreach (var update in
call.ResponseStream.ReadAllAsync(cts.Token))
{
Console.WriteLine($"Price: {update.Price}");
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Stream cancelled.");
}元数据:请求标头与尾部元数据
gRPC 支持元数据(键值对):在响应之前作为 headers 发送,在响应之后作为尾部元数据发送。您可以使用它们传递身份验证令牌、跟踪 ID 或分页游标。
// Server: send initial metadata
await context.WriteResponseHeadersAsync(new Metadata
{
{ "x-correlation-id", Guid.NewGuid().ToString() }
});
// Server: set trailing metadata
context.ResponseTrailers.Add("x-total-count", "1500");
// Client: read headers
var headers = await call.ResponseHeadersAsync;
var trailerId = headers.GetValue("x-correlation-id");面向浏览器 Clients 的 gRPC-Web
浏览器无法使用原生 gRPC(不支持 HTTP/2 尾部元数据)。请使用 Grpc.AspNetCore.Web,以及 grpc-web JavaScript client 或 Blazor 的 Grpc.Net.Client.Web。
// dotnet add package Grpc.AspNetCore.Web
app.UseGrpcWeb();
app.MapGrpcService<GreeterService>().EnableGrpcWeb();
// Blazor client:
var handler = new GrpcWebHandler(GrpcWebMode.GrpcWeb,
new HttpClientHandler());
using var channel = GrpcChannel.ForAddress(
"https://localhost:7042",
new GrpcChannelOptions { HttpHandler = handler });真实场景:日志流式传输服务
日志聚合服务使用服务端流式传输,实时向已连接的监控客户端传送应用程序日志条目。
public override async Task StreamLogs(
LogStreamRequest request,
IServerStreamWriter<LogEntry> responseStream,
ServerCallContext context)
{
await foreach (var log in _logChannel.Reader.ReadAllAsync(
context.CancellationToken))
{
if (log.Level >= request.MinLevel)
{
await responseStream.WriteAsync(new LogEntry
{
Level = log.Level.ToString(),
Message = log.Message,
Timestamp = Timestamp.FromDateTime(log.Timestamp)
});
}
}
}快速检查
.proto 文件中使用哪个关键字表示服务端流式传输 RPC?
回顾:一元与服务端流式传输 RPC
要点:
- 一元 RPC:一个请求,一个响应,是最常见的模式
- 服务端流式传输:一个请求,随时间发送多条响应消息
- 通过写入
IServerStreamWriter<T>实现服务端流式传输 - 在
ReadAllAsync()上使用await foreach消费服务端流 - 使用 CancellationToken 从任意一侧干净地停止流
- gRPC-Web 允许浏览器客户端调用 gRPC 服务
常见问题解答
「一元与服务器流式 RPC」课时是免费的吗?
是的 — 「一元与服务器流式 RPC」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。
「一元与服务器流式 RPC」这节课中我会学到什么?
实现一元调用和服务器端流式传输,让服务器实时向客户端推送数据。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C# Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「一元与服务器流式 RPC」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C# Academy 课中编写并运行代码吗?
能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- gRPC 与 Protobuf 基础
- 一元与服务器流式 RPC
- 客户端与双向流式传输
- 截止时间、取消与拦截器