gRPC:异步API教程

这篇教程介绍了如何在C++中使用gRPC的异步API来创建简单的服务器和客户端。通过CompletionQueue进行异步操作,包括客户端的初始化RPC、等待回复和状态,以及服务器端的请求处理和响应。示例基于Greeter应用,涉及了CallData类来管理RPC状态。
摘要由CSDN通过智能技术生成

Asynchronous-API tutorial

异步API教程

This tutorial shows you how to write a simple server and client in C++ using gRPC’s asynchronous/non-blocking APIs. It assumes you are already familiar with writing simple synchronous gRPC code, as described in Basics tutorial. The example used in this tutorial follows from the basic Greeter example used in the quick start. You’ll find it along with installation instructions in grpc/examples/cpp/helloworld.

​本教程展示如何使用gRPC的异步/非阻塞API在C++中编写一个简单的服务器和客户端。假设已经熟悉编写简单的同步gRPC代码,如基础教程中所述。本教程中使用的示例来自快速入门中使用的基本Greeter示例。可以在grpc/examples/cpp/helloworld中找到它以及安装说明。

Overview

概述

gRPC uses the CompletionQueue API for asynchronous operations. The basic work flow is as follows:

​gRPC使用CompletionQueue API进行异步操作。基本工作流程如下:

  • bind a CompletionQueue to an RPC call
  • 将CompletionQueue绑定到RPC调用
  • do something like a read or write, present with a unique void* tag
  • 做一些读或写的事情,用一个独特的void*标签表示
  • call CompletionQueue::Next to wait for operations to complete. If a tag appears, it indicates that the corresponding operation is complete.
  • 调用CompletionQueue::Next以等待操作完成。如果出现标记,则表示相应的操作已完成。

Async client

异步客户端

To use an asynchronous client to call a remote method, you first create a channel and stub, just as you do in a synchronous client. Once you have your stub, you do the following to make an asynchronous call:

​要使用异步客户端调用远程方法,首先要创建一个通道和存根,就像在同步客户端中一样。一旦有了存根,就可以执行以下操作来进行异步调用:

  • Initiate the RPC and create a handle for it. Bind the RPC to a CompletionQueue.

  • 初始化RPC并为其创建句柄。将RPC绑定到CompletionQueue。

    CompletionQueue cq;
    std::unique_ptr<ClientAsyncResponseReader<HelloReply> > rpc(
        stub_->AsyncSayHello(&context, request, &cq));
    
  • Ask for the reply and final status, with a unique tag

  • 使用唯一标签询问回复和最终状态

    Status status;
    rpc->Finish(&reply, &status, (void*)1);
    
  • Wait for the completion queue to return the next tag. The reply and status are ready once the tag passed into the corresponding Finish() call is returned.

  • 等待完成队列返回下一个标记。一旦返回传递给相应Finish()调用的标记,应答和状态就准备好了。

    void* got_tag;
    bool ok = false;
    cq.Next(&got_tag, &ok);
    if (ok && got_tag == (void*)1) {
      // check reply and status
    }
    

You can see the complete client example in greeter_async_client.cc.

​可以在greetiner_async_client.cc中看到完整的客户端示例。

Async server

异步服务器

The server implementation requests an RPC call with a tag and then waits for the completion queue to return the tag. The basic flow for handling an RPC asynchronously is:

服务器实现请求带有标记的RPC调用,然后等待完成队列返回标记。异步处理RPC的基本流程是:

  • Build a server exporting the async service

  • 建立导出异步服务的服务器

    helloworld::Greeter::AsyncService service;
    ServerBuilder builder;
    builder.AddListeningPort("0.0.0.0:50051", InsecureServerCredentials());
    builder.RegisterService(&service);
    auto cq = builder.AddCompletionQueue();
    auto server = builder.BuildAndStart();
    
  • Request one RPC, providing a unique tag

  • 请求一个RPC,提供唯一标记

    ServerContext context;
    HelloRequest request;
    ServerAsyncResponseWriter<HelloReply> responder;
    service.RequestSayHello(&context, &request, &responder, &cq, &cq, (void*)1);
    
  • Wait for the completion queue to return the tag. The context, request and responder are ready once the tag is retrieved.

  • 等待完成队列返回标记。一旦检索到标签,上下文、请求和响应程序就准备好了。

    HelloReply reply;
    Status status;
    void* got_tag;
    bool ok = false;
    cq.Next(&got_tag, &ok);
    if (ok && got_tag == (void*)1) {
      // set reply and status
      responder.Finish(reply, status, (void*)2);
    }
    
  • Wait for the completion queue to return the tag. The RPC is finished when the tag is back.

  • 等待完成队列返回标记。RPC在标记返回时完成。

    void* got_tag;
    bool ok = false;
    cq.Next(&got_tag, &ok);
    if (ok && got_tag == (void*)2) {
      // clean up
    }
    

This basic flow, however, doesn’t take into account the server handling multiple requests concurrently. To deal with this, our complete async server example uses a CallData object to maintain the state of each RPC, and uses the address of this object as the unique tag for the call.

然而,这个基本流程没有考虑服务器同时处理多个请求。为了解决这个问题,我们完整的异步服务器示例使用CallData对象来维护每个RPC的状态,并使用该对象的地址作为调用的唯一标记。

class CallData {
public:
  // Take in the "service" instance (in this case representing an asynchronous
  // server) and the completion queue "cq" used for asynchronous communication
  // with the gRPC runtime.
//取“service”实例(在本例中表示异步
//服务器)和用于异步通信的完成队列“cq”
//使用gRPC运行时。
  CallData(Greeter::AsyncService* service, ServerCompletionQueue* cq)
      : service_(service), cq_(cq), responder_(&ctx_), status_(CREATE) {
    // Invoke the serving logic right away.
    //立即调用服务逻辑。
    Proceed();
  }

  void Proceed() {
    if (status_ == CREATE) {
      // As part of the initial CREATE state, we *request* that the system
      // start processing SayHello requests. In this request, "this" acts are
      // the tag uniquely identifying the request (so that different CallData
      // instances can serve different requests concurrently), in this case
      // the memory address of this CallData instance.
    //作为初始CREATE状态的一部分,我们*请求*系统
    //开始处理SayHello请求。在本请求中,“this”行为是
    //唯一标识请求的标签(以便不同的CallData
    //实例可以同时为不同的请求提供服务),在这种情况下
    //此CallData实例的内存地址。
      service_->RequestSayHello(&ctx_, &request_, &responder_, cq_, cq_,
                                this);
      // Make this instance progress to the PROCESS state.
      //使此实例进入PROCESS状态。
      status_ = PROCESS;
    } else if (status_ == PROCESS) {
      // Spawn a new CallData instance to serve new clients while we process
      // the one for this CallData. The instance will deallocate itself as
      // part of its FINISH state.
    //生成一个新的CallData实例,在我们处理时为新客户提供服务
    //这个CallData。实例将自身解除分配为
    //完成状态的一部分。
      new CallData(service_, cq_);

      // The actual processing.
      //实际处理。
      std::string prefix("Hello ");
      reply_.set_message(prefix + request_.name());

      // And we are done! Let the gRPC runtime know we've finished, using the
      // memory address of this instance as the uniquely identifying tag for
      // the event.
    //我们完了!让gRPC运行时知道我们已经完成,使用
    //此实例的内存地址作为的唯一标识标签
    //事件。
      responder_.Finish(reply_, Status::OK, this);
      status_ = FINISH;
    } else {
      GPR_ASSERT(status_ == FINISH);
      // Once in the FINISH state, deallocate ourselves (CallData).
      //一旦进入FINISH状态,就释放我们自己(CallData)。
      delete this;
    }
  }
}

For simplicity the server only uses one completion queue for all events, and runs a main loop in HandleRpcs to query the queue:

为了简单起见,服务器对所有事件只使用一个完成队列,并在HandleRpcs中运行一个主循环来查询队列:

void HandleRpcs() {
  // Spawn a new CallData instance to serve new clients.
  //生成一个新的CallData实例来为新客户端提供服务。
  new CallData(&service_, cq_.get());
  void* tag;  // uniquely identifies a request.唯一地标识请求。
  bool ok;
  while (true) {
    // Block waiting to read the next event from the completion queue. The
    // event is uniquely identified by its tag, which in this case is the
    // memory address of a CallData instance.
    //阻止等待从完成队列中读取下一个事件。这个
    //事件由其标记唯一标识,在本例中为
    //CallData实例的内存地址。
    cq_->Next(&tag, &ok);
    GPR_ASSERT(ok);
    static_cast<CallData*>(tag)->Proceed();
  }
}
Shutting Down the Server
关闭服务器

We’ve been using a completion queue to get the async notifications. Care must be taken to shut it down after the server has also been shut down.

我们一直在使用一个完成队列来获取异步通知。在服务器也关闭后,必须小心关闭它。

Remember we got our completion queue instance cq_ in ServerImpl::Run() by running cq_ = builder.AddCompletionQueue(). Looking at ServerBuilder::AddCompletionQueue’s documentation we see that

请记住,我们通过运行cq_=builder.AddCompletionQueue()在ServerImpl::Run()中获得了完成队列实例cq_。查看ServerBuilder::AddCompletionQueue的文档,我们可以看到

… Caller is required to shutdown the server prior to shutting down the returned completion queue.

…要求调用方在关闭返回的完成队列之前关闭服务器。

Refer to ServerBuilder::AddCompletionQueue’s full docstring for more details. What this means in our example is that ServerImpl's destructor looks like:

有关更多详细信息,请参阅ServerBuilder::AddCompletionQueue的完整文档字符串。在我们的例子中,这意味着ServerImpl的析构函数看起来像:

~ServerImpl() {
  server_->Shutdown();
  // Always shutdown the completion queue after the server.
  //始终在服务器之后关闭完成队列。
  cq_->Shutdown();
}

You can see our complete server example in greeter_async_server.cc.

​可以在greetiner_async_server.cc中看到我们完整的服务器示例。

Last modified February 17, 2022: rename RegisterAsyncService to RegisterService (#891) (8f2378a)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值