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をたどってください。