【问题标题】:How do I use select() and gRPC to create a server?如何使用 select() 和 gRPC 创建服务器?
【发布时间】:2016-09-21 19:15:51
【问题描述】:

我需要在单线程应用程序中使用 gRPC(带有额外的套接字通道)。天真地,我正在考虑使用 select() 并根据弹出的文件描述符调用 gRPC 来处理消息。我的问题是,谁能给我一个粗略的(5-10 行代码)大纲框架,说明在 select() 弹出后我需要调用什么?

在同步情况下查看 Google 的“hello world”示例意味着线程池(我不能使用),而在异步情况下显示主循环阻塞——这对我不起作用,因为我需要处理其他套接字操作。

【问题讨论】:

  • 任何使用select()的教程都应该展示它的基本用法。
  • 其实 select() 不是问题(我知道怎么用)——问题是我在 gRPC 中调用哪些函数来“处理”弹出的文件描述符,并保证gRPC 不会阻止等待更多数据。
  • 这可能是不可能的。许多 RPC 库提供的高级方法可能不允许您将 I/O 和函数调用步骤分开。
  • 嗨,Barmar,你是对的。根据gRPC discussion group,我正在尝试做的事情无法完成。 :-(
  • 有点旁白,但是 select() 是邪恶的,你应该(几乎)永远不要调用它,因为 poll() 非常便携,如果你有太多 FD 不会随机爆炸你的过程。一个例外是如果您需要 pselect() - 遗憾的是, ppoll() 不是很便携。

标签: c++ sockets select grpc


【解决方案1】:

在这一点上(可能永远)你做不到。

事件循环的一大弱点,包括直接使用 select()/poll() 风格的 API,是它们不能以任何自然方式组合,除非两者之间直接集成。

理论上,我们可以为 Linux 添加这样的功能——导出一个带有 timerfd 的 epoll_fd,如果调用完成队列是有效的,那么它变得可读,但是这样做会对堆栈的其余部分施加大量限制和架构开销只是为了在 Linux 上支持这个用例。其他任何地方都需要一个后台线程来管理该 fd 的可读性。

【讨论】:

    【解决方案2】:

    这可以通过使用 gRPC 异步服务和 grpc::Alarm 将来自 select 或其他轮询 API 的任何事件发送到 gRPC 完成队列来完成。您可以在this gist 中看到同时使用 Epoll 和 gRPC 的示例。重要的功能是这两个:

    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);
    }
    

    在这里您可以看到tick_loops 函数反复调用epoll_wait,直到不再返回任何事件。对于每个 epoll 事件,都会构造一个 grpc::Alarm,并将截止日期设置为现在。之后,gRPC 事件循环立即被grpc_tick 泵送。

    请注意,grpc::Alarm 实例必须在完成队列中超过其时间。在实际应用中,警报应该以某种方式附加到标签(本例中为event.data.ptr),以便可以在完成回调中清除。

    然后再次对 gRPC 事件循环进行抽水,以确保所有非 epoll 事件也得到处理。

    完成队列是线程安全的,因此您也可以将 epoll 泵放在一个线程上,将 gRPC 泵放在另一个线程上。使用此设置,您无需将每个轮询超时设置为 0,就像在此示例中一样。这将通过限制事件循环泵的干循环来减少 CPU 使用率。

    【讨论】:

      猜你喜欢
      • 2021-09-26
      • 1970-01-01
      • 2022-08-19
      • 2021-12-17
      • 2019-09-14
      • 1970-01-01
      • 1970-01-01
      • 2021-08-28
      • 2021-08-04
      相关资源
      最近更新 更多