简体   繁体   English

如何使用 select() 和 gRPC 创建服务器?

[英]How do I use select() and gRPC to create a server?

I need to use gRPC but in a single-threaded application (with additional socket channels).我需要在单线程应用程序中使用 gRPC(具有额外的套接字通道)。 Naively, I'm thinking of using select() and depending on which file descriptor pops, calling gRPC to handle the message.天真地,我正在考虑使用 select() 并根据弹出的文件描述符调用 gRPC 来处理消息。 My question is, can someone give me a rough (5-10 lines of code) outline skeleton on what I need to call after the select() pops?我的问题是,有人可以给我一个粗略的(5-10 行代码)大纲框架,说明在 select() 弹出后我需要调用什么吗?

Looking at Google's "hello world" example in the synchronous case implies a thread pool (which I can't use), and in the asynchronous case shows the main loop blocking -- which doesn't work for me because I need to handle other socket operations.在同步情况下查看 Google 的“hello world”示例暗示了一个线程池(我无法使用),在异步情况下显示了主循环阻塞——这对我不起作用,因为我需要处理其他套接字操作。

You can't do it, at this point (and probably ever).在这一点上(并且可能永远),您不能这样做。

One of the big weaknesses of event loops, including direct use of select()/poll() style APIs, is that they aren't composable in any natural way short of direct integration between the two.事件循环的一大弱点,包括直接使用 select()/poll() 样式的 API,是它们不能以任何自然方式组合,除非两者之间没有直接集成。

We could theoretically add such functionality for Linux -- exporting an epoll_fd with a timerfd which becomes readable if it would be productive to call into a completion queue, but doing so would impose substantial constraints and architectural overhead on the rest of the stack just to support this usecase only on Linux.理论上我们可以为 Linux 添加这样的功能——导出一个带有 timerfd 的 epoll_fd,如果调用完成队列有效率,它就会变得可读,但这样做会对堆栈的其余部分施加大量约束和架构开销,只是为了支持此用例仅适用于 Linux。 Everywhere else would require a background thread to manage that fd's readability.其他任何地方都需要一个后台线程来管理该 fd 的可读性。

This can be done using a gRPC async service along with grpc::Alarm to send any events that come from select or other polling APIs onto the gRPC completion queue.这可以使用 gRPC 异步服务和grpc::Alarm来完成,以将来自select或其他轮询 API 的任何事件发送到 gRPC 完成队列。 You can see an example using Epoll and gRPC together in this gist .您可以在本要点中看到一起使用 Epoll 和 gRPC 的示例。 The important functions are these two:重要的功能是这两个:

bool grpc_tick(grpc::ServerCompletionQueue& queue) {
  void* tag = nullptr;
  bool ok = false;
  auto next_status = queue.AsyncNext(&tag, &ok, std::chrono::system_clock::now());
  if (next_status == grpc::CompletionQueue::GOT_EVENT) {
    if (ok && tag) {
      static_cast<RequestProcessor*>(tag)->grpc_queue_tick();
    } else {
      std::cerr << "Not OK or bad tag: " << ok << "; " << tag << std::endl;
      return false;
    }
  }
  return next_status != grpc::CompletionQueue::SHUTDOWN;
}

bool tick_loops(int epoll, grpc::ServerCompletionQueue& queue) {
  // Pump epoll events over to gRPC's completion queue.
  epoll_event event{0};
  while (epoll_wait(epoll, &event, /*maxevents=*/1, /*timeout=*/0)) {
    grpc::Alarm alarm;
    alarm.Set(&queue, std::chrono::system_clock::now(), event.data.ptr);
    if (!grpc_tick(queue)) return false;
  }

  // Make sure gRPC gets at least 1 tick.
  return grpc_tick(queue);
}

Here you can see the tick_loops function repeatedly calls epoll_wait until no more events are returned.这里可以看到tick_loops function 反复调用epoll_wait直到没有更多的事件返回。 For each epoll event, a grpc::Alarm is constructed with the deadline set to right now.对于每个 epoll 事件,都会构建一个grpc::Alarm ,并将截止日期设置为现在。 After that, the gRPC event loop is immediately pumped with grpc_tick .之后,gRPC 事件循环立即被grpc_tick

Note that the grpc::Alarm instance MUST outlive its time on the completion queue.请注意, grpc::Alarm实例必须比其在完成队列中的时间更长。 In a real-world application, the alarm should be somehow attached to the tag ( event.data.ptr in this example) so it can be cleaned up in the completion callback.在真实世界的应用程序中,警报应该以某种方式附加到标签(本例中为event.data.ptr ),以便可以在完成回调中清除它。

The gRPC event loop is then pumped again to ensure that any non-epoll events are also processed.然后 gRPC 事件循环再次被抽取以确保任何非 epoll 事件也被处理。

Completion queues are thread safe, so you could also put the epoll pump on one thread and the gRPC pump on another.完成队列是线程安全的,因此您也可以将 epoll 泵放在一个线程上,将 gRPC 泵放在另一个线程上。 With this setup you would not need to set the polling timeouts for each to 0 as they are in this example.使用此设置,您无需像本例中那样将每个轮询超时设置为0 This would reduce CPU usage by limiting dry cycles of the event loop pumps.这将通过限制事件循环泵的干循环来减少 CPU 使用率。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM