poll/epoll/kqueueを任意に切り替えられるコード

ネットワークで通信するプログラムを書いていると、ファイルディスクリプタ(ネットワークならソケット)をselectやpollで監視して、パケットが届いたら何かする、ということが良くあります。
しかしselectやpollは、*BSD で kqueue・kevent を使ってみようで書かれているように、どうも遅いらしい。C10K問題が取りざたされている昨今、Linuxにはepoll、BSDにはkqueue、Solarisには/dev/pollというより高速な仕組みが用意されているのですが、epollを使ってしまうとLinuxでしか動かないし、kqueueで書くとBSDでしか動かない。
というわけで、epollもkqueueも同じインターフェースで使えて、#defineで簡単に中身を切り替えられると嬉しい、とは誰しも一度は思うはず。そうに違いない。


もちろん先人も同じことを考えており、libeventなるものがあります。libeventは一般的なpoll/select、Linuxのepoll、BSDのkqueue、Solarisの/dev/poll、さらにWindowsの(?)にも対応しているという素晴らしいものなのですが、以下に挙げる問題があります。

  • 関数ポインタを使って実装を切り替えているので、インライン化できない
  • 抽象化度が高め
  • インターフェースがC。C++じゃない
  • 自分で書きたい


というわけで、インライン化できて、柔軟に使えて、C++なインターフェースで使えるクラスを作っています。↓こんな感じで使えます。

typedef Multiplexer<true, false> Multiplexer;     // oneshot = true,  threadsafe = false
Multiplexer mp;

int listen_sock;
// listen_scok = socket()してbind()してlisten()して…

mp.add(listen_sock, Multiplexer::EVENT_IN);
// listen_sockを監視対象に追加

try {

        while(1) {
                Multiplexer::events_type events( mp.wait() );    // イベントが届くまで待つ…

                for(Multiplexer::events_type::ev_t ev(events.wait());
                                ev.fd != -1;
                                ev = events.next() ) {

                        if( ev.fd == m_listen_sock ) {
                                int new_sock = accept(listen_sock);
                                mp.add(new_sock, Multiplexer::EVENT_IN);
                        } else {
                                // たぶんev.fdにデータが届いています
                                hoge(ev.fd);
                        }

                        // template引数のoneshotオプションがtrueなので、
                        // 1回イベントが届いたら、監視されなくなる
                        // Multiplexer::reactivateで再度監視対象にする
                        mp.reactivate(ev, Multiplexer::EVENT_IN);
                }
        }

} catch (...) {
        // エラーです
}

まずはpollとepollがLinuxで、kqueueがFreeBSDで動きました。kqueueはMac OS Xでも使えるはずなのですが、何故か動きません。
fd_typeを抽象化しておいたので、たぶん同じインターフェースでWindows版も作れます。


ソースはここのMercurialのWebインターフェースからたどれます。最新の更新の「files:」をクリックすると、管理されているソースコードが一覧で表示されるので、src/lib/multiplexer_*.hをたどってください。