简体   繁体   English

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

[英]Multi-threaded Server handling multiple clients in one thread

I wanted to create a multi-threaded socket server using C++11 and standard linux C-Librarys. 我想用C ++ 11和标准的Linux C-Librarys创建一个多线程套接字服务器。

The easiest way doing this would be opening a new thread for each incoming connection, but there must be an other way, because Apache isn't doing this. 这样做的最简单方法是为每个传入连接打开一个新线程,但必须有另一种方法,因为Apache没有这样做。 As far as I know Apache handles more than one connection in a Thread. 据我所知,Apache在Thread中处理多个连接。 How to realise such a system? 如何实现这样的系统?

I thought of creating one thread always listening for new clients and assigning this new client to a thread. 我想创建一个线程总是监听新客户端并将这个新客户端分配给一个线程。 But if all threads are excecuting an "select()" currently, having an infinite timeout and none of the already assigned client is doing anything, this could take a while for the client to be useable. 但是如果当前所有线程都正在执行“select()”,具有无限超时并且没有任何已分配的客户端正在执行任何操作,则可能需要一段时间才能使客户端可用。

So the "select()" needs a timeout. 所以“select()”需要超时。 Setting the timeout to 0.5ms would be nice, but I guess the workload could rise too much, couldn't it? 将超时设置为0.5ms会很好,但我猜工作量可能会上升太多,不是吗?

Can someone of you tell me how you would realise such a system, handling more than one client for each thread? 有人可以告诉我你将如何实现这样一个系统,为每个线程处理多个客户端? PS: Hope my English is well enough for you to understand what I mean ;) PS:希望我的英语足以让你理解我的意思;)

The standard method to multiplex multiple requests onto a single thread is to use the Reactor pattern. 将多个请求复用到单个线程的标准方法是使用Reactor模式。 A central object (typically called a SelectServer, SocketServer, or IOService), monitors all the sockets from running requests and issues callbacks when the sockets are ready to continue reading or writing. 中心对象(通常称为SelectServer,SocketServer或IOService)监视运行请求中的所有套接字,并在套接字准备好继续读取或写入时发出回调。

As others have stated, rolling your own is probably a bad idea. 正如其他人所说,滚动自己可能是一个坏主意。 Handling timeouts, errors, and cross platform compatibility (eg epoll for linux, kqueue for bsd, iocp for windows) is tricky. 处理超时,错误和跨平台兼容性(例如linux的epoll,bsd的kqueue,windows的iocp)是棘手的。 Use boost::asio or libevent for production systems. 对生产系统使用boost :: asio或libevent。

Here is a skeleton SelectServer (compiles but not tested) to give you an idea: 这是一个骨架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_;
};

First off, have a look at using poll() instead of select() : it works better when you have large number of file descriptors used from different threads. 首先,看一下使用poll()而不是select() :当你从不同的线程中使用大量文件描述符时,它会更好。

To get threads currently waiting in I/O out of waiting I'm aware of two methods: 为了让当前在I / O中等待的线程无法等待,我知道有两种方法:

  1. You can send a suitable signal to the thread using pthread_kill() . 您可以使用pthread_kill()向线程发送合适的信号。 The call to poll() fails and errno is set to EINTR . poll()的调用失败,并且errno设置为EINTR
  2. Some systems allow a file descriptor to be obtained from a thread control device. 一些系统允许从线程控制设备获得文件描述符。 poll() ing the corresponding file descriptor for input succeeds when the thread control device is signalled. 当线程控制设备被发信号通知时, poll()用于输入的相应文件描述符成功。 See, eg, Can we obtain a file descriptor for a semaphore or condition variable? 请参阅,例如, 我们可以获取信号量或条件变量的文件描述符吗? .

This is not a trivial task. 这不是一项微不足道的任务。

In order to achieve that, you need to maintain a list of all opened sockets (the server socket and the sockets to current clients). 为了实现这一点,您需要维护所有已打开套接字的列表(服务器套接字和当前客户端的套接字)。 You then use the select() function to which you can give a list of sockets (file descriptors). 然后使用select()函数,您可以为其提供套接字列表(文件描述符)。 With correct parameters, select() will wait until any event happen on one of the sockets. 使用正确的参数,select()将等待,直到其中一个套接字发生任何事件。

You then must find the socket(s) which caused select() to exit and process the event(s). 然后,您必须找到导致select()退出并处理事件的套接字。 For the server socket, it can be a new client. 对于服务器套接字,它可以是新客户端。 For client sockets, it can be requests, termination notification, etc. 对于客户端套接字,它可以是请求,终止通知等。

Regarding what you say in your question, I think you are not understanding the select() API very well. 关于你在问题中所说的内容,我认为你并没有很好地理解select() API。 It is OK to have concurrent select() calls in different threads, as long as they are not waiting on the same sockets. 可以在不同的线程中进行并发select()调用,只要它们不在相同的套接字上等待即可。 Then if the clients are not doing anything, it doesn't prevent the server select() from working and accepting new clients. 然后,如果客户端没有执行任何操作,则不会阻止服务器select()工作并接受新客户端。

You only need to give select() a timeout if you want to be able to do things even if clients are not doing anything. 如果您希望能够执行某些操作,即使客户端没有执行任何操作,您也只需要给select()一个超时。 For example, you may have a timer to send periodic infos to the clients. 例如,您可能有一个计时器来向客户端发送定期信息。 You then give select a timeout corresponding to you first timer to expire, and process the expired timer when select() returns (along with any other concurrent events). 然后,您选择与您的第一个计时器相对应的超时到期,并在select()返回时处理过期的计时器(以及任何其他并发事件)。

I suggest you have a long read of the select manpage. 我建议您仔细阅读精选联机帮助页。

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

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