ミニマルなHTTPサーバ

http://0xcc.net/blog/archives/000178.html
このコードには長時間動作させる上で明らかにマズい点が二つあります。
(22:58追記: 問題のコードは既に修正されています。高林さん、すばやい対応ありがとうございます。)
UNIXネットワークプログラミング」の別の節に解説はありますが、コードだけコピペした人が困らないよう指摘しておきます。


一つ目はSIGPIPEシグナルの発生に備えていない点です。
サーバーがレスポンスを返す前にクライアントがソケットを閉じてしまうと、write()がSIGPIPEシグナルを生成することがあります。
SIGPIPEの既定の動作はプロセスの終了なので、この状況が発生しただけでサーバーは勝手に(coreを残さず)終了してしまいます。
通常は次のような関数でSIGPIPEを無視します。

#include <cstring>
#include <signal.h>

void ignore_sigpipe()
{
    struct sigaction act;

    // 処理系依存の追加メンバに備え、ゼロクリア
    std::memset(&act, 0, sizeof(act));

    act.sa_handler = SIG_IGN;
    sigemptyset(&act.sa_mask);

    if (::sigaction(SIGPIPE, &act, 0) == -1)
        handle_error(); // 適当なエラー処理
}

この結果、write()がSIGPIPEで失敗した場合にerrnoにEPIPEがセットさせるようになります。
UNIXネットワークプログラミング」では5.13節に解説があります。


二つ目はaccept()のエラー処理に関してです。
accept()はよくエラーを返す関数なので、エラー即終了というのはよくありません。

  • TCPのハンドシェイクが完了してaccept()が呼ばれるまでの間にコネクションが切断された場合、errnoにECONNABORTEDがセットされる
  • プロセスがシグナルを受信しaccept()が中断された場合、errnoにEINTRがセットされる

後者はaccept()に限った話ではないので重箱の隅ですが、EINVALのような明らかなプログラミングエラーを除いて、accept()がエラーを返してもリトライする必要があります。
また、環境によっては別のエラーコードがセットされる場合もあります。
http://www.linux.or.jp/JM/html/LDP_man-pages/man2/accept.2.html
UNIXネットワークプログラミング」では5.11節に解説があります。


まとめ

  • SIGPIPEは無視する
  • accept()がエラーを返してもリトライする