0

C++11と標準のLinuxC-Libraryを使用してマルチスレッドソケットサーバーを作成したかったのです。

これを行う最も簡単な方法は、着信接続ごとに新しいスレッドを開くことですが、Apacheはこれを行っていないため、別の方法が必要です。私の知る限り、Apacheはスレッド内で複数の接続を処理します。そのようなシステムをどのように実現するのですか?

常に新しいクライアントをリッスンする1つのスレッドを作成し、この新しいクライアントをスレッドに割り当てることを考えました。ただし、すべてのスレッドが現在「select()」を実行していて、タイムアウトが無限であり、すでに割り当てられているクライアントが何も実行していない場合、クライアントが使用可能になるまでに時間がかかることがあります。

したがって、「select()」にはタイムアウトが必要です。タイムアウトを0.5msに設定するといいのですが、ワークロードが大きくなりすぎるのではないでしょうか。

スレッドごとに複数のクライアントを処理して、そのようなシステムをどのように実現するかを誰かに教えてもらえますか?PS:私の英語があなたが私の意味を理解するのに十分であることを願っています;)

4

3 に答える 3

3

複数のリクエストを 1 つのスレッドに多重化する標準的な方法は、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_;
};
于 2012-09-02T16:02:01.023 に答える
0

まず、poll()代わりに使用する方法を見てみselect()ましょう。さまざまなスレッドから多数のファイル記述子を使用している場合に、より効果的に機能します。

現在I/Oで待機しているスレッドを待機から解放するには、次の2つの方法を認識しています。

  1. を使用して、適切なシグナルをスレッドに送信できますpthread_kill()。の呼び出しはpoll()失敗し、errnoに設定されEINTRます。
  2. 一部のシステムでは、ファイル記述子をスレッド制御デバイスから取得できます。poll()スレッド制御デバイスに信号が送られると、対応するファイル記述子を入力に使用できます。たとえば、セマフォまたは条件変数のファイル記述子を取得できますか?を参照してください。
于 2012-09-02T15:25:01.117 に答える
0

これは簡単な作業ではありません。

これを実現するには、開いているすべてのソケット (サーバー ソケットと現在のクライアントへのソケット) のリストを維持する必要があります。次に、ソケット (ファイル記述子) のリストを指定できるselect()関数を使用します。正しいパラメータを使用すると、select() はソケットの 1 つでイベントが発生するまで待機します。

次に、select() を終了させて​​イベントを処理する原因となったソケットを見つける必要があります。サーバー ソケットの場合は、新しいクライアントにすることができます。クライアント ソケットの場合は、リクエスト、終了通知などです。

あなたの質問の内容については、select() API をよく理解していないと思います。同じソケットで待機していない限り、異なるスレッドでselect()を同時に呼び出すことは問題ありません。クライアントが何もしていない場合でも、サーバーの select() が動作し、新しいクライアントを受け入れることを妨げません。

クライアントが何もしていない場合でも、何かを実行できるようにしたい場合にのみ、select() にタイムアウトを与える必要があります。たとえば、定期的な情報をクライアントに送信するタイマーがあるとします。次に、期限が切れる最初のタイマーに対応するタイムアウトを select に与え、select() が (他の同時イベントと共に) 戻ったときに期限切れのタイマーを処理します。

selectマンページをよく読んでおくことをお勧めします。

于 2012-09-02T15:59:54.243 に答える