2007年02月18日(日) [長年日記]
■ [net] select()を通過したのにrecvfrom()でブロック
Fedora Core 5でUDPを扱っていて、select()は通過したのにその後のrecvfrom()でブロックしてしまいプログラムが止まってしまうという現象に遭遇した。
selectのバグ?
Linux では、 select() がソケットファイルディスクリプタで "読み込みの準備ができた" と報告した場合でも、この後で read を行うと停止 (block) することがある。このような状況は、例えば、データが到着したが、検査でチェックサム異常が見つかり廃棄された時などに起こりえる。他にもファイルディスクリプタが準備できたと間違って報告される状況が起こるかもしれない。 したがって、停止すべきではないソケットに対しては O_NONBLOCK を使うとより安全であろう。
というバグの記述がある。検索すると、天泣記でこの件について少し調べられていた。
はじめはLinuxのこの問題を踏んでしまったのかと思ったが、調べた結果、今回遭遇したのはこの問題ではなかったようだ。
IP_RECVERRソケットオプション
件の問題が起こるプログラムでは、IP_RECVERRソケットオプションが使われていた。このオプションの説明はip(7)のmanにある。この説明を読んだだけではよく分からなかったが、試してみたところ次のように動作するようだ。
- このオプションを設定していると、そのソケットから送信したメッセージに関するICMPエラーを同ソケットで受信できるようになる。
- ICMPエラーを受信した後でそのソケットに対しrecvfrom()やsendto()を呼び出すと、この呼び出しは1度だけエラーになる。このときのエラーコードは(後で書く)。
- 受信したICMPエラーは、recvfrom()やrcvmsg()の呼び出し時に MSG_ERRQUEUE フラグを指定すると読み出すことができる(recvのmanに説明あり)。ソケットには通常のメッセージ用とICMP用の2つの受信用キューがあり、recvfrom()やrcvmsg()はMSG_ERRQUEUEフラグがオフだと通常メッセージ用キューから、オンだとICMP用キューから読み出すという動作をするようだ。
- recvfrom()やrcvmsg()は到着メッセージが無い場合に通常はブロックするが、MSG_ERRQUEUEフラグがオンの場合は到着メッセージが無いとエラーでリターンする。このときのエラーコードは(後で書く)。
- ICMPエラーメッセージをソケットから読み出さない限り、そのソケットに対するselect()はブロックしなくなる。
で、元のプログラムは
- select()
- MSG_ERRQUEUEフラグオフのrecvfrom()
- 上のrecvfrom()がエラーだったらMSG_ERRQUEUEフラグオンのrecvmsg()
という流れになっており、selectを通過した原因がICMPメッセージの到着だった場合にブロックしてしまっていた。これを、
- select()
- MSG_ERRQUEUEフラグオンのrecvmsg()
- 上のrecvmsg()がエラーだったら(ICMPエラーが到着していなかったら)、MSG_ERRQUEUEフラグオフのrecvfrom()
という流れに変更し、対処したつもり。エラーが無い場合にrecvが2回呼ばれるので効率が悪いけれど、この直し方しか思いつかない。
ああ、ソケットをノンブロッキングにすればこんな無駄な処理はしなくていいのか。先に書いたバグの件もあるので、ノンブロッキングI/Oを使うべきなのかなあ。
■ やること
確定申告- 年金
- 税務署へ行く
- PDF生成