C++ 多线程服务器在一个线程中处理多个客户端

C++ 多线程服务器在一个线程中处理多个客户端,c++,linux,sockets,c++11,C++,Linux,Sockets,C++11,我想使用C++11和标准的LinuxC-Library创建一个多线程套接字服务器 最简单的方法是为每个传入连接打开一个新线程,但必须有另一种方法,因为Apache没有这样做。据我所知,Apache在一个线程中处理多个连接。如何实现这样一个系统 我想创建一个线程,总是监听新客户机,并将这个新客户机分配给一个线程。但是,如果所有线程当前都在执行“select()”,有一个无限的超时,并且已经分配的客户机都没有执行任何操作,那么客户机可能需要一段时间才能使用 因此,“select()”需要一个超时。将

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

最简单的方法是为每个传入连接打开一个新线程,但必须有另一种方法,因为Apache没有这样做。据我所知,Apache在一个线程中处理多个连接。如何实现这样一个系统

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

因此,“select()”需要一个超时。将超时设置为0.5ms会很好,但我想工作量可能会增加太多,不是吗

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

首先,看看如何使用
poll()
而不是
select()
:如果使用大量来自不同线程的文件描述符,效果会更好

要使当前在I/O中等待的线程退出等待,我知道两种方法:

  • 您可以使用
    pthread\u kill()
    向线程发送合适的信号。调用
    poll()
    失败,并且
    errno
    设置为
    EINTR
  • 一些系统允许从线程控制设备获取文件描述符<代码>轮询()当线程控制设备发出信号时,为输入设置相应的文件描述符成功。参见,例如

  • 首先,看一看如何使用
    poll()
    而不是
    select()
    :如果使用大量来自不同线程的文件描述符,效果会更好

    要使当前在I/O中等待的线程退出等待,我知道两种方法:

  • 您可以使用
    pthread\u kill()
    向线程发送合适的信号。调用
    poll()
    失败,并且
    errno
    设置为
    EINTR
  • 一些系统允许从线程控制设备获取文件描述符<代码>轮询()当线程控制设备发出信号时,为输入设置相应的文件描述符成功。参见,例如

  • 这不是一项微不足道的任务

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

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

    关于您在问题中所说的,我认为您对select()API的理解不是很好。在不同的线程中有并发select()调用是可以的,只要它们不在同一个套接字上等待。然后,如果客户机没有执行任何操作,则不会阻止服务器select()工作并接受新客户机

    如果您想在客户端不做任何事情的情况下也能做一些事情,只需要给select()一个超时。例如,您可能有一个计时器向客户端发送定期信息。然后,为select指定一个与要过期的第一个计时器相对应的超时,并在select()返回时处理过期的计时器(以及任何其他并发事件)


    我建议您长时间阅读select手册。

    这不是一项简单的任务

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

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

    关于您在问题中所说的,我认为您对select()API的理解不是很好。在不同的线程中有并发select()调用是可以的,只要它们不在同一个套接字上等待。然后,如果客户机没有执行任何操作,则不会阻止服务器select()工作并接受新客户机

    如果您想在客户端不做任何事情的情况下也能做一些事情,只需要给select()一个超时。例如,您可能有一个计时器向客户端发送定期信息。然后,为select指定一个与要过期的第一个计时器相对应的超时,并在select()返回时处理过期的计时器(以及任何其他并发事件)


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

    将多个请求多路传输到单个线程的标准方法是使用Reactor模式。一个中心对象(通常称为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_;
    };
    
    #包括
    #包括
    #包括
    类选择服务器{
    公众:
    枚举就绪类型{
    可读性=0,
    可写=1
    };
    void CallWhenReady(ReadyType类型、int-fd、std::函数闭包){
    承插架