acceptが返す新しいソケット、待受け側の設定と分けて考える

緑の細い入口を持つ白い受付台の横に、青い印の付いた桃色の小さなカプセルが独立して置かれている。

サーバーが接続を受け付けるとき、待受けに使っていたソケットが、そのまま一人の相手との通信用に変わるのでしょうか。Linuxのacceptは、新しい接続を扱う別のソケットを返します。この区別を見落とすと、「待受けを非ブロッキングにしたから、受け取った接続も同じ設定のはず」という思い込みにつながります。

ここでは、接続を待つ側と、受け付けた後に通信する側を分けて見ます。どちらを待たせたくないのかを明確にすると、acceptとaccept4のフラグの役割も読みやすくなります。

簡単にまとめると?

  • acceptは待受けソケットを残したまま、新しい接続用ソケットのファイルディスクリプターを返します。
  • Linuxでは、待受け側のO_NONBLOCKがacceptで得た接続へ自動的に引き継がれるとは考えられません。新しい接続に必要な設定を明示します。
  • accept4のSOCK_NONBLOCKは新しい接続側の指定です。accept自体で待ちたくない場合は、渡す待受けソケット側の設定も必要です。

受付口と、相手ごとの通信口

ソケットは通信の端点で、プログラムはファイルディスクリプターという番号を通じて操作します。接続型の通信では、サーバー側に待受けの役割があり、個々の接続にはその接続を通じてデータをやり取りする役割があります。[1][2]

acceptが成功すると、待っている接続要求を取り出して、新しい接続済みソケットを指す番号が返ります。返されたソケットは待受け状態ではなく、元の待受けソケットはそのまま残ります。[1] 受付口を一人の利用者へ渡して閉めるのではなく、受付口を残し、その人用の通信口を受け取る、と考えると役割を整理できます。

たとえば、説明用に待受けをL、受け付けた接続をCと呼びます。Lで次の接続を受け付ける処理と、Cで要求データを読む処理は、同じ待ち方を指定しているとは限りません。この名前は図のための記号であり、実際の番号や性能を示すものではありません。

「待たない」の対象を二つに分ける

非ブロッキングとは、今すぐ処理を進められない場面で、その呼び出しを待機させ続けずに結果を返す設定です。acceptについては、受け付けられる接続がないとき、待受けソケットが通常のブロッキング設定なら接続を待ちます。非ブロッキングなら、EAGAINまたはEWOULDBLOCKを返す場面があります。[1]

ここで設定しているのはL側です。一方、受け付けた後のCでデータを読むときにも待ち続けたくないなら、C側にも必要な設定を行います。Lの受付とCの読取りは、同じ処理ではありません。どちらの番号をどの呼び出しへ渡しているかまで見ないと、「非ブロッキングにした」という説明だけでは不足します。

Linuxでは、待受けのフラグを前提にしない

Linuxのacceptで返される新しいソケットは、O_NONBLOCKやO_ASYNCといったファイル状態フラグを、待受け側から引き継ぎません。これは他の実装と差がある部分で、移植を考えるプログラムでは、引継ぎの有無を当てにせず、返された接続に必要なフラグを明示する方針が重要です。[1]

accept4では、引数flagsへSOCK_NONBLOCKを指定し、新しい接続側のO_NONBLOCKを設定できます。指定先は返される接続に対応するオープンファイル記述です。SOCK_CLOEXECも別に指定でき、こちらは新しいディスクリプターのclose-on-exec設定を扱います。[1] 受付時の待ち方と、別のプログラムへ実行を切り替える際の引継ぎは、それぞれ別の目的です。

待受けLの非ブロッキング設定と、accept4が返す接続Cの設定は別。LがブロッキングならSOCK_NONBLOCK指定でも受付は待ち得る。
独自の状態整理。受け付ける接続がない場合を左、成功してCを得た場合を右で比較した。実機計測や処理順序の時間軸ではない。

二つの指定を組み合わせて確かめる

図では三つの構成を比べています。第一は、Lを非ブロッキングにし、通常のacceptを使う場合です。受付待ちは避けられても、Linuxで得るCのO_NONBLOCKは、Lから引き継がれるものではありません。第二は、Lをブロッキングのままにし、accept4へSOCK_NONBLOCKを渡す場合です。成功後のCには設定できますが、接続が来るまでの受付自体は待つことがあります。第三は、Lも非ブロッキングにし、accept4へSOCK_NONBLOCKを渡す構成です。受付と新しい接続の両方を明示している点が違います。

この三行を仕様に沿った状態表としてプログラムでも検査しました。確認したのは、LとCの二つの設定を独立した値として追えることです。Linuxカーネル上でaccept4を実行した測定ではなく、接続数や処理速度の改善を示す実験でもありません。実際のサーバーでは成功した戻り値とエラーを確認し、受け取った番号を正しく管理する必要があります。

通知された後でも、接続が待っているとは限らない

selectやpollなどが待受けソケットを読取り可能と知らせても、acceptを呼ぶまでに別スレッドが接続を受け付けたり、非同期のネットワークエラーが起きたりする場合があります。通知時点の状態が、その後も残っている保証にはなりません。[1]

そのため、イベントを受け取った後のacceptでも待機を避けたいなら、L側を非ブロッキングにする理由があります。「通知があったから必ずすぐ成功する」と決めず、接続がなくなった結果も扱います。ただし、全てのエラーを同じ意味にして再試行するのは適切ではありません。ディスクリプター不足や不正な引数などは、接続待ちとは別の原因です。

最後に、フラグが保存される場所にも注意が必要です。ファイル状態フラグを持つオープンファイル記述と、それを指すディスクリプターは区別されます。[3] 本稿のLとCはacceptによって役割の異なるソケットを得る例です。設定を点検するときは、どのソケットのどの状態を、どの呼び出しで設定したのかを記録すると、受付の停止と通信中の停止を切り分けやすくなります。

出典

  1. Linux man-pages 6.19、accept(2)、DESCRIPTION、VERSIONS、NOTES、ERRORS。
  2. 同、socket(2)、DESCRIPTION。ソケットと接続型通信の基本。
  3. 同、open(2)、DESCRIPTION。ファイルディスクリプターとオープンファイル記述。図と状態表は独自作成。