TCP/IPでsend()を繰り返す際に気をつけると良いこと
久しぶりにネットワークプログラミングネタで。
TCP/IPをソケットインターフェースで使う際に、通信相手側が切断したかどうかはrecv()で受信したときにゼロが戻ることで判断するのは誰もが知っていることですが、send()で送信する際にも、1回目にはエラーにならなくても2回目には大抵エラーになることでわかるということも知っていると思います。
たとえば、HTTPサーバを作るとすると、送受信部分は下記のような感じに書くでしょう。
ループ{
recv()でリクエストを受信
if(recv()のリターン値がゼロか-1){
break;
}
send()で応答を送信:応答を送りきるまで
}
大抵はこれで問題ないのですが、高負荷時にはこれだとカーネルが不機嫌になることがわかりました。。
send()で大きなサイズを送信する場合、クライアント側が途中で切断してしまうことがあり、基本的にはsend()が-1リターンしたときに中断すれば良いだろうと思っていたのですが、それだとカーネルが不機嫌になるのです。
正解は、send()と同時にselect(),poll()などで受信・エラーのレディを監視し、レディならrecv()して確認し、ゼロか-1なら切れたと判断してsend()もやめる、という感じのようです。
send()もいつかはエラーになるのですが、ガンガン送信しているとそれまでの間にカーネル内の送信バッファにどんどんたまってしまうようで、カーネルがとても不機嫌になるのと、なかなか途中で切れたTCPコネクションの処理が終わらない、という状況になるような感じで気がつきました。
おそらく、トータルで1Gbps程度までの処理ならsend()のエラーで判断するくらいでも大丈夫だと思いますが、それを超えてくるとrecv()で早めに切れたことを確認して中断しないと不機嫌になるようです。
他にも、SYNパケットを変に送るとおかしくなったりするなど、超高負荷だといろいろと見慣れない現象が現れるものなのですが、さすがにそこまでいくとマニアックすぎるので、このくらいで。。