簡體   English   中英

多線程服務器在一個線程中處理多個客戶端

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

我想用C ++ 11和標准的Linux C-Librarys創建一個多線程套接字服務器。

這樣做的最簡單方法是為每個傳入連接打開一個新線程,但必須有另一種方法,因為Apache沒有這樣做。 據我所知,Apache在Thread中處理多個連接。 如何實現這樣的系統?

我想創建一個線程總是監聽新客戶端並將這個新客戶端分配給一個線程。 但是如果當前所有線程都正在執行“select()”,具有無限超時並且沒有任何已分配的客戶端正在執行任何操作,則可能需要一段時間才能使客戶端可用。

所以“select()”需要超時。 將超時設置為0.5ms會很好,但我猜工作量可能會上升太多,不是嗎?

有人可以告訴我你將如何實現這樣一個系統,為每個線程處理多個客戶端? PS:希望我的英語足以讓你理解我的意思;)

將多個請求復用到單個線程的標准方法是使用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_;
};

首先,看一下使用poll()而不是select() :當你從不同的線程中使用大量文件描述符時,它會更好。

為了讓當前在I / O中等待的線程無法等待,我知道有兩種方法:

  1. 您可以使用pthread_kill()向線程發送合適的信號。 poll()的調用失敗,並且errno設置為EINTR
  2. 一些系統允許從線程控制設備獲得文件描述符。 當線程控制設備被發信號通知時, poll()用於輸入的相應文件描述符成功。 請參閱,例如, 我們可以獲取信號量或條件變量的文件描述符嗎?

這不是一項微不足道的任務。

為了實現這一點,您需要維護所有已打開套接字的列表(服務器套接字和當前客戶端的套接字)。 然后使用select()函數,您可以為其提供套接字列表(文件描述符)。 使用正確的參數,select()將等待,直到其中一個套接字發生任何事件。

然后,您必須找到導致select()退出並處理事件的套接字。 對於服務器套接字,它可以是新客戶端。 對於客戶端套接字,它可以是請求,終止通知等。

關於你在問題中所說的內容,我認為你並沒有很好地理解select() API。 可以在不同的線程中進行並發select()調用,只要它們不在相同的套接字上等待即可。 然后,如果客戶端沒有執行任何操作,則不會阻止服務器select()工作並接受新客戶端。

如果您希望能夠執行某些操作,即使客戶端沒有執行任何操作,您也只需要給select()一個超時。 例如,您可能有一個計時器來向客戶端發送定期信息。 然后,您選擇與您的第一個計時器相對應的超時到期,並在select()返回時處理過期的計時器(以及任何其他並發事件)。

我建議您仔細閱讀精選聯機幫助頁。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM