gRPC Server 端请求处理流程

gRPC Server 端请求处理流程

初始化

  1. 创建并启动 ServerTransport

在 Server 启动的时候,最终调用 NettyServerstart() 方法,为 ServerBootstrap 添加了 ChannelInitializer,最终,当有新的连接建立时,会由 NettyServerHandler 调用该类的 initChannel 方法,初始化一个 NettyServerTransport

  • io.grpc.netty.NettyServer#start

在初始化 Netty Channel 时,会先创建 NettyServerTransport,然后调用监听器的 Transport 创建事件,添加一个超时取消任务;
然后会调用 Transportstart 方法启动 Transport

b.childHandler(new ChannelInitializer<Channel>() {
   
   
    @Override
    public void initChannel(Channel ch) {
   
   
        // 构建基于 Netty 的 ServerTransport
        NettyServerTransport transport = new NettyServerTransport(/*...*/);
        ServerTransportListener transportListener;

        synchronized (NettyServer.this) {
   
   
            // 调用监听器回调,Transport 创建事件
            transportListener = listener.transportCreated(transport);
        }

        // 启动监听器
        transport.start(transportListener);
        ChannelFutureListener loopReleaser = new LoopReleaser();
        channelDone.addListener(loopReleaser);
        ch.closeFuture().addListener(loopReleaser);
    }
});
  • io.grpc.netty.NettyServerTransport#start

在启动 Transport 时,会为当前的 Transport 创建一个处理器,并绑定到 Netty 的 Channel 中

public void start(ServerTransportListener listener) {
   
   
    this.listener = listener;

    // 为 pipeline 创建 Netty Handler
    grpcHandler = createHandler(listener, channelUnused);

    // 创建 Handler
    ChannelHandler negotiationHandler = protocolNegotiator.newHandler(grpcHandler);
    ChannelHandler bufferingHandler = new WriteBufferingAndExceptionHandler(negotiationHandler);

    // 添加监听器
    ChannelFutureListener terminationNotifier = new TerminationNotifier();
    channelUnused.addListener(terminationNotifier);
    channel.closeFuture().addListener(terminationNotifier);

    channel.pipeline().addLast(bufferingHandler);
}

处理请求

当 Server 与 Client 的连接建立成功之后,可以开始处理请求

请求整体处理流程
  1. 读取 Settings 帧,触发 Transport ready 事件
  2. 读取 Header 帧,触发 FrameListener#onHeadersRead 事件
    1. NettyServerHandler 处理
      1. 根据 Header 里面的信息,获取相应的方法
      2. 将 HTTP 流转换为 NettyServerStream
      3. 触发 Transport#streamCreated 事件
        1. 检查编解码、解压缩等信息,创建可取消的上下文
        2. 初始化流监听器
        3. 提交 StreamCreated 任务
      4. 触发 NettyServerStream.TransportState#onStreamAllocated 事件
        1. 提交 OnReady 任务
  3. 执行 StreamCreated 任务
    1. 根据方法名查找方法定义
    2. 调用 startCall 开始处理
      1. 遍历拦截器,使用拦截器包装方法处理器
      2. 调用 startWrappedCall 处理
        1. 创建 ServerCallImpl 实例
        2. 通过方法定义的请求处理器 startCall 方法处理
          1. 创建响应观察器 ServerCallStreamObserverImpl 实例
          2. 调用 call.request() 获取指定数量的消息
            1. 提交 RequestRunnable 任务获取指定数量的消息
          3. 创建调用监听器 UnaryServerCallListener
        3. 创建 ServerStreamListenerImpl 流监听器实例
  4. 执行 OnReady 任务
    1. 调用 UnaryServerCallListener#onReady 处理 Ready 事件
      1. 修改 ready 状态
      2. 如果有 onReadyHandler 任务,则执行
  5. 执行 RequestRunnable 任务
    1. 要求指定数量的消息
    2. 修改等待投递的消息数量
    3. 调用 deliver 方法投递
      1. 如果有待投递的消息,根据类型进行投递
        1. 当消息类型是消息体时,处理消息体
          1. 读取消息体的流
          2. 调用 MessageFramer.Listener#messagesAvailable 事件,通知新的消息
          3. 提交 MessagesAvailable 任务
  6. 调用 MessageDeframer#close 方法关闭帧
    1. 调用流监听器半关闭事件
    2. 提交 HalfClosed 任务
  7. 执行 MessagesAvailable 任务
  8. MessageProducer 中获取消息,解析为请求对象
  9. 调用 SeverCall.Listener#onMessage 方法处理消息
    1. request 对象赋值给相应的对象,该对象会在 halfClose 时处理
  10. 执行 HalfClosed 任务
    1. 调用 invoke 方法,处理业务逻辑
      1. 根据方法 ID,使用相应的实现调用业务逻辑
        1. 调用 StreamObserver#onNext 发送响应
          1. 发送响应 Header
            1. 设置编码和压缩的请求头
            2. 写入 Header
          2. 发送响应 body
            1. 将响应对象序列化为流
            2. 写入响应
            3. 清空缓存
        2. 调用 StreamObserver#onComplete 完成请求
          1. 使用 OK 状态关闭调用
            1. 修改关闭状态
            2. 调用流关闭事件
              1. 关闭帧
              2. 将响应状态加入响应元数据中
              3. 修改 TransportState 的状态
              4. 写入响应元数据,发送给客户端
    2. 冻结响应
    3. 如果 ready 状态,再次执行 onReady 事件
    4. 当流关闭时,调用 TransportState#complete 事件
      1. 关闭监听器
      2. 提交 Closed 任务
    5. 执行 Closed 任务
      1. 调用 stream#complete 事件
      2. 取消上下文
1. 读取 Settings 帧
  • io.grpc.netty.NettyServerHandler.FrameListener#onSettingsRead

当读取到 Settings 帧时,会调用 onSettingsRead 方法,同时会同时 Transport 监听器 ready 事件

public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) {
   
   
    if (firstSettings) {
   
   
        firstSettings = false;
        // 通知 Transport ready
        attributes = transportListener.transportReady(negotiationAttributes);
    }
}
  • io.grpc.internal.ServerImpl.ServerTransportListenerImpl#transportReady

会通知 Transport Ready 事件,会遍历 ServerTransportFilter 通知,默认没有 ServerTransportFilter 的实现

public Attributes transportReady(Attributes attributes) {
   
   
    // 如果有握手超时回调,则取消
    handshakeTimeoutFuture.cancel(false);
    handshakeTimeoutFuture = null;

    // 遍历 TransportFilter,通知 ready 事件并获取 attributes
    for (ServerTransportFilter filter : transportFilters) {
   
   
        attributes = Preconditions.checkNotNull(filter.transportReady(attributes), "Filter %s returned null", filter);
    }
    this.attributes = attributes;
    return attributes;
}
2. 接收 header

当 Server 接收到 Client 发送的 header 后,经过 Netty 处理,最终调用 onHeadersRead 开始处理流

  • io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler.FrameListener#onHeadersRead

接收 header 帧

public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) throws Http2Exception {
   
   
    if (NettyServerHandler.this.keepAliveManager != null) {
   
   
        NettyServerHandler.this.keepAliveManager.onDataReceived();
    }

    NettyServerHandler.this.onHeadersRead(ctx, streamId, headers);
}
  • io.grpc.netty.NettyServerHandler.FrameListener#onHeadersRead

开始处理 header

public void onHeadersRead(ChannelHandlerContext ctx,
                          int streamId,
                          Http2Headers headers,
                          int streamDependency,
                          short weight,
                          boolean exclusive,
                          int padding,
                          boolean endStream) throws Http2Exception {
   
   
    if (keepAliveManager != null) {
   
   
        keepAliveManager.onDataReceived();
    }
    // 最终会创建流并出发流创建事件
    NettyServerHandler.this.onHeadersRead(ctx, streamId, headers);
}
  • io.grpc.netty.NettyServerHandler#onHeadersRead

会根据请求的 Header 信息,查找服务和方法,校验请求类型,请求方法,传输编码等内容;然后根据 HTTP2 流 Id,获取对应的流,将其转换为 NettyServerStream;调用 TransportonStreamCreated 事件
向线程池中提交 StreamCreated,然后调用 onStreamAllocated 方法通知流 StreamListeneronReady事件提交OnReady任务

private void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers) throws Http2Exception {
   
   
  try {
   
   
    // 删除斜杠获取方法限定名称
    CharSequence path = headers.path();

    // 方法限定名,即包含服务名和方法名
    String method = path.subSequence(1, path.length()).toString();
    // 获取 HTTP 流
    Http2Stream http2Stream = requireHttp2Stream(streamId);

    // 将 header 转为 metadata
    Metadata metadata = Utils.convertHeaders(headers);
    // 创建支持统计的上下文
    StatsTraceContext statsTraceCtx = StatsTraceContext.newServerContext(streamTracerFactories, method, metadata);

    // 创建流的声明
    NettyServerStream.TransportState state = new NettyServerStream.TransportState(
            this,
            ctx.channel().eventLoop(),
            http2Stream,
            maxMessageSize,
            statsTraceCtx,
            transportTracer,
            method);

    try {
   
   
      // 获取请求的 authority
      String authority = getOrUpdateAuthority((AsciiString) headers.authority());
      // 创建 Server 端的流
      NettyServerStream stream = new NettyServerStream(ctx.channel(),
              state,
              attributes,
              authority,
              statsTraceCtx,
              transportTracer);

      // 触发监听器,通知流创建事件,查找相应处理器,开始处理流,会提交 StreamCreated 任务到线程池中
      transportListener.streamCreated(stream, method, metadata);
      // 会提交 OnReady 任务到线程池中,通知 Stream Ready
      state.onStreamAllocated();
      http2Stream.setProperty(streamKey, state);
    }
  } catch (Exception e) {
   
   
    logger.log(Level.WARNING, "Exception in onHeadersRead()", e);
    throw newStreamException(streamId, e);
  }
}
3. 流创建事件
transportListener.streamCreated(stream, method, metadata);
  • io.grpc.internal.ServerImpl.ServerTransportListenerImpl#streamCreated

检查并初始化流的编解码,解压缩等信息;创建可需取消的上下文,选择要执行的线程池,初始化流监听器,最终提交流创建任务

private void streamCreatedInternal(final ServerStream stream,
                                   final String methodName,
                                   final Metadata headers,
                                   final Tag tag) {
   
   
    final Executor wrappedExecutor;

    if (executor == directExecutor()) {
   
   
        wrappedExecutor = new 
<think>我们正在处理一个关于调试gRPCServer项目的问题。用户想要调试作为类库的gRPC Server项目在C#WPF应用程序中的运行问题。根据引用[3]:WPF调用Grpc。...服务grpc就搭建好了。[^3]我们可以知道,gRPC服务项目(类库)需要被宿主应用程序(如WPF应用程序)调用。因此,调试的步骤应该包括设置宿主应用程序(WPF)来启动服务,并允许我们调试服务代码。 具体步骤:1.**确保解决方案结构正确**:通常,解决方案中会包含以下几个项目:-gRPC服务定义类库(包含.proto文件和生成的代码)-gRPC服务实现类库(实现服务定义,也就是我们的Server类库)-宿主应用程序(如WPF应用程序,用于承载gRPC服务)-客户应用程序(如另一个WPF应用程序,用于测试服务)注意:在同一个解决方案中,宿主应用程序会引用gRPC服务实现类库。 2. **设置启动项目**:由于gRPC服务作为类库,不能直接启动,我们需要将宿主应用程序(WPF应用程序)设置为启动项目。3.**在宿主应用程序中启动gRPC服务**:根据引用[3],在宿主应用程序中(如WPF应用程序)需要配置gRPC服务。例如,在Program.cs(如果是控制台宿主)或App.xaml.cs(WPF)中,我们需要创建并启动gRPC服务器。例如,在WPF应用程序中,我们可以在App.xaml.cs的OnStartup方法中启动gRPC服务器:```csharppublicpartialclass App: Application{privateGrpc.Core.Server _grpcServer;protectedoverride voidOnStartup(StartupEventArgs e){base.OnStartup(e);//创建gRPC服务器并启动_grpcServer= newGrpc.Core.Server{Services= {Greeter.BindService(new GreeterServiceImpl()) },// GreeterServiceImpl在服务实现类库中Ports ={ newGrpc.Core.ServerPort("localhost",50051, Grpc.Core.ServerCredentials.Insecure)}};_grpcServer.Start();}protectedoverride voidOnExit(ExitEventArgs e){_grpcServer?.ShutdownAsync().Wait();base.OnExit(e);}}```注意:实际代码可能有所不同,取决于你的服务定义和宿主方式。 4. **设置断点**:在gRPC服务实现类库的方法中设置断点。5.**启动调试**:启动宿主应用程序(WPF应用程序),当客户调用服务时,断点将会被命中。6.**使用客户测试**:为了触发服务的代码,我们需要一个客户来发送请求。这个客户可以是同一个解决方案中的另一个项目(如另一个WPF项目),或者使用一个测试客户(如Grpcurl,Postman等)。在调试时,我们可以运行客户项目(需要设置多个启动项目)或者手动运行一个客户程序。设置多个启动项目的方法:在解决方案资源管理器中右键解决方案->属性->通用属性->启动项目,选择“多个启动项目”,将宿主应用程序和客户应用程序都设置为启动。7.**附加到进程**:如果客户不是同一个解决方案中的项目,或者我们无法同时启动多个项目,我们可以先启动宿主应用程序(WPF),然后启动客户程序,然后使用Visual Studio的“调试”->“附加到进程”来附加到宿主应用程序的进程,这样断点也会被命中。注意事项:-确保gRPC服务和客户使用的口一致,并且服务已经正确启动。-如果服务没有启动,检查宿主应用程序中启动gRPC服务器的代码是否正确执行。根据用户的问题,他可能已经搭建好了gRPC服务(作为类库)和WPF宿主应用程序,现在需要调试服务的方法。按照上述步骤,应该能够进行调试。相关问题:1.如何设置VisualStudio以同时启动多个项目(宿主和客户)?2.在WPF应用程序中,除了在App.xaml.cs中启动gRPC服务器,还有其他方式吗? 3.如何确保gRPC服务器在后台运行而不阻塞WPF的UI线程? 4.如果gRPC服务断点没有被命中,可能的原因有哪些?</think>### 调试gRPC Server类库项目的步骤 以下是调试作为类库的gRPC Server项目在C# WPF应用程序中的具体方法: 1. **设置多项目启动** - 在解决方案资源管理器中右键解决方案 → 选择"属性" - 在"启动项目"中选择"多个启动项目" - 将WPF客户gRPC服务项目都设置为"启动" - 确保服务项目先于客户启动(使用右侧箭头调整顺序) 2. **配置Kestrel调试** ```csharp // 在gRPC服务项目的Program.cs中 builder.WebHost.ConfigureKestrel(options => { options.ListenLocalhost(5000, o => o.Protocols = HttpProtocols.Http2); }); ``` 添加此配置允许Visual Studio附加调试器[^3] 3. **添加调试断点** - 在gRPC服务实现类的方法中设置断点 ```csharp public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context) { // 在此处设置断点 ↓ return Task.FromResult(new HelloReply { Message = "Hello " + request.Name }); } ``` 4. **使用进程附加调试** - 先启动gRPC服务项目(控制台输出监听地址) - 在VS菜单中选择"调试" → "附加到进程" - 选择服务进程(通常为项目名.exe) - 再启动WPF客户项目发起gRPC调用 5. **日志诊断配置** ```csharp // 在appsettings.json中添加 "Logging": { "LogLevel": { "Default": "Debug", "Microsoft.AspNetCore": "Warning" } } ``` 可在输出窗口查看详细通信日志 6. **异常捕获设置** - 在VS菜单:调试 → 窗口 → 异常设置 - 勾选"Common Language Runtime Exceptions" - 确保所有gRPC相关异常能被捕获 **注意事项**: 1. 确保服务和客户使用相同协议(HTTP/2) 2. 检查防火墙是否放行服务口(默认5000/5001) 3. 使用`Grpc.Core.Interceptors`添加自定义拦截器辅助调试 4. 对于异步调用,使用`System.Diagnostics.Debugger.Break()`强制中断 调试示意图: ``` [WPF客户] --gRPC调用--> [gRPC服务类库] ↑ | |------调试器附加-------↓ (共享同一个VS调试会话) ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值