【问题标题】:Multi-threaded Server handling multiple clients in one thread多线程服务器在一个线程中处理多个客户端
【发布时间】:2012-09-02 14:51:46
【问题描述】:

我想使用 C++11 和标准 linux C-Librarys 创建一个多线程套接字服务器。

执行此操作的最简单方法是为每个传入连接打开一个新线程,但必须有其他方法,因为 Apache 不这样做。据我所知,Apache 在一个线程中处理多个连接。如何实现这样的系统?

我想创建一个线程,始终监听新客户端并将这个新客户端分配给一个线程。但是,如果所有线程当前都在执行“select()”,具有无限超时且已分配的客户端都没有执行任何操作,则客户端可能需要一段时间才能使用。

所以“select()”需要超时。将超时设置为 0.5 毫秒会很好,但我想工作量可能会增加太多,不是吗?

谁能告诉我,你们将如何实现这样一个系统,为每个线程处理多个客户端? PS:希望我的英语足够好让你明白我的意思;)

【问题讨论】:

  • 我做了,但我发现的只是为每个连接使用一个新线程的示例
  • "我想使用 C++11 和标准 linux C-Librarys 创建一个多线程套接字服务器。" 为什么?像理智的人一样使用Boost.Asio。 ;-]
  • 如果我找不到自己开发的方法,我会这样做;)但我想自己创建它,因为我是 C++ 新手,我喜欢构建这样的东西来让语言知道:)
  • 有很多有用的信息 - stackoverflow.com/questions/11687215/…

标签: c++ linux sockets c++11


【解决方案1】:

将多个请求多路复用到单个线程的标准方法是使用反应器模式。一个中心对象(通常称为 SelectServer、SocketServer 或 IOService)监控正在运行的请求的所有套接字,并在套接字准备好继续读取或写入时发出回调。

正如其他人所说,自己滚动可能不是一个好主意。处理超时、错误和跨平台兼容性(例如 linux 的 epoll、bsd 的 kqueue、windows 的 iocp)是很棘手的。将 boost::asio 或 libevent 用于生产系统。

这里是一个骨架 SelectServer(编译但未测试)给你一个想法:

#include <sys/select.h>

#include <functional>
#include <map>

class SelectServer {
 public:
  enum ReadyType {
    READABLE = 0,
    WRITABLE = 1
  };

  void CallWhenReady(ReadyType type, int fd, std::function<void()> closure) {
    SocketHolder holder;
    holder.fd = fd;
    holder.type = type;
    holder.closure = closure;
    socket_map_[fd] = holder;
  }

  void Run() {
    fd_set read_fds;
    fd_set write_fds;
    while (1) {
      if (socket_map_.empty()) break;

      int max_fd = -1;
      FD_ZERO(&read_fds);
      FD_ZERO(&write_fds);
      for (const auto& pr : socket_map_) {
        if (pr.second.type == READABLE) {
          FD_SET(pr.second.fd, &read_fds);
        } else {
          FD_SET(pr.second.fd, &write_fds);
        }
        if (pr.second.fd > max_fd) max_fd = pr.second.fd;
      }

      int ret_val = select(max_fd + 1, &read_fds, &write_fds, 0, 0);
      if (ret_val <= 0) {
        // TODO: Handle error.
        break;
      } else {
        for (auto it = socket_map_.begin(); it != socket_map_.end(); ) {
          if (FD_ISSET(it->first, &read_fds) ||
              FD_ISSET(it->first, &write_fds)) {
            it->second.closure();
            socket_map_.erase(it++);
          } else {
            ++it;
          }
        }
      }
    }
  }

 private:
  struct SocketHolder {
    int fd;
    ReadyType type;
    std::function<void()> closure;
  };

  std::map<int, SocketHolder> socket_map_;
};

【讨论】:

    【解决方案2】:

    首先,看看使用poll() 而不是select():当您使用来自不同线程的大量文件描述符时,它会更好地工作。

    为了让当前在 I/O 中等待的线程不再等待,我知道有两种方法:

    1. 您可以使用pthread_kill() 向线程发送合适的信号。对 poll() 的调用失败,errno 被设置为 EINTR
    2. 某些系统允许从线程控制设备获取文件描述符。 poll()输入对应的文件描述符在线程控制设备发出信号时成功。参见,例如,Can we obtain a file descriptor for a semaphore or condition variable?

    【讨论】:

    • 我喜欢第二个想法 :) 我需要一些时间来理解你的意思,但我想这应该很好用,谢谢 :)
    【解决方案3】:

    这不是一项简单的任务。

    为了实现这一点,您需要维护所有打开的套接字(服务器套接字和当前客户端的套接字)的列表。然后,您可以使用 select() 函数来提供套接字列表(文件描述符)。使用正确的参数,select() 将一直​​等待,直到其中一个套接字发生任何事件。

    然后您必须找到导致 select() 退出并处理事件的套接字。对于服务器套接字,它可以是一个新的客户端。对于客户端套接字,可以是请求、终止通知等。

    关于您在问题中所说的话,我认为您不太了解 select() API。在不同的线程中同时调用 select() 是可以的,只要它们不在同一个套接字上等待。然后,如果客户端不做任何事情,它不会阻止服务器 select() 工作并接受新客户端。

    如果您希望即使客户端没有做任何事情也能做某事,您只需要给 select() 一个超时时间。例如,您可能有一个定时器来定期向客户端发送信息。然后,您给 select 一个对应于您的第一个计时器过期的超时,并在 select() 返回时处理过期的计时器(以及任何其他并发事件)。

    我建议您仔细阅读 select 手册页。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-24
      • 1970-01-01
      • 2018-04-28
      • 1970-01-01
      • 2014-12-21
      • 2011-04-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多