0Pricing
C# Academy · 课时

一元与服务器流式 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. gRPC 与 Protobuf 基础
  2. 一元与服务器流式 RPC
  3. 客户端与双向流式传输
  4. 截止时间、取消与拦截器
← 返回 C# Academy