VOS 17.1 以前のバージョンでは、サーバーアプリケーションが accept 関数を呼び出さない限り、STCP は TCP 接続要求を受け付けませんでした。接続要求はバックログキューに格納されましたが、accept 関数の呼び出しによってその要求がバックログキューから取り除かれるまで、応答は送信されませんでした。バックログキューがいっぱいになると、STCP は接続要求に対してリセットを送信するようになっていました。
リリース 17.1 以降、TCP 接続の挙動が変更されました。 現在、接続要求が到着した際、バックログキューが満杯でない場合、STCPスタックは直ちに接続を受け入れる応答を送信します。その後、その接続はバックログキューに格納されます。バックログキューが満杯になると、それ以降の接続要求に対しては応答が送信されなくなります。サーバーアプリケーションが accept を呼び出すと、バックログキューの先頭にある接続が削除されます。この動作は、他のほとんどのオペレーティングシステム上のTCPスタックの動作と同様になりました。
通常、この動作の変化によって、サーバーやクライアントアプリケーションの挙動に目立った変化が生じることはありません。しかし、サーバーアプリケーションの処理速度が低下したり、予想以上に接続が集中したりした場合、接続がバックログキューに長時間滞留することがあります。このような状況下では、いくつかの興味深い挙動が見られることがあります。
以下の表は、クライアントの動作に基づいて接続要求がバックログキューに格納された際の処理の概要を示しています。クライアントは、サーバーアプリケーションからのデータ送信を待つだけで何もしない場合、データを送信する場合、あるいはFIN(終了)フラグ(正常な終了)またはRST(リセット)フラグ(異常な終了)を付けて接続を閉じる場合があります。最初の4つの列は、予想通りの動作をします。 サーバーアプリケーションは、バックログキューからソケットを取得するために accept を呼び出し、その後 recv を呼び出します。データがない場合は処理が停止し、ソケット内の内容に応じてデータやファイル終了の通知を受け取ります。より注意深く確認する必要があるのは、最後の4つの列です。
| ソケットの中に何も入っていない | ソケット内のデータ | ソケット内のフィン | socket/ 内のデータ
ソケット内のフィン |
socket/ 内のデータ
ソケット内のFIN/ クライアントソケットが消失しました |
ソケット内のFIN/
クライアントソケットが消失しました |
socket/ 内のデータ
送信済み |
リセット 送信 | ||
| 同意する | ソケットを返す | ソケットを返す | ソケットを返す | ソケットを返す | ソケットを返す | ソケットを返す | ハング | ハング | |
| 最初の受信 | ハング | データを返す | EOFを返す | データを返す | リターンがリセットエラーを返す | EOFを返す | |||
| 2回目の受信 | ハング | EOFを返す |
ソケット内のデータ / ソケット内のFIN / クライアントソケットの消失
このシナリオでは、クライアントがデータを送信した後、接続をシャットダウンしています。STCPのバージョン17.1では、送信されたすべてのデータに対してACKを送信しますが、受信キュー内のデータがアプリケーションに渡されるまではFINに対してACKを送信しません。その結果、クライアントのFINは、クライアントの再送信タイマーが切れるまで再送信され続けます。 適切に記述されたクライアントアプリケーションは、サーバーアプリケーションが接続の自側をシャットダウンするのを待機するため、FINがタイムアウトした際にクライアントはエラー通知を受け取ります。しかし、クライアントアプリケーションがサーバーからの合図を待たずにソケットを閉じてしまうと、送信したデータが失われた可能性があることを認識できません。
サーバーアプリケーションがacceptを呼び出した際、重複した確認応答パケットが送信されると、データに対する確認応答は行われるものの、FINに対する確認応答は行われません。クライアントはすでにソケットを解放しているため、resetで応答します。このresetにより、サーバー側のソケットはクリアされます。 サーバーアプリケーションは、acceptの呼び出しから最初のrecvの呼び出しまでの時間差に応じて、最初のrecv呼び出しか2番目のrecv呼び出しのいずれかでresetエラーを受け取ることになります。その時間が、クライアントからのresetパケットを受信して処理するのにかかる時間よりも短い場合、サーバーアプリケーションはデータを確認できます。いずれの場合でも、サーバーアプリケーションは接続が確立されたこと、およびそれが中断されたことを認識します。
ソケット内のFIN / クライアントソケットの消失
これは、ソケット内にデータが存在しないという点で、前述のシナリオとは異なります。データが存在しない場合、FINは確認応答されます。acceptはソケットを返し、recvはFINを受信したことを示すEOFを返します。ソケットへの書き込みや、ソケットの閉じ操作を含むいかなる操作も、クライアントが対応するソケットを保持しなくなったため、resetが返されます。
ソケット内のデータ / リセット送信
ここでは、クライアントがデータを送信した後、リセットを送信します。リセットが送信される理由は多岐にわたりますが、一般的なものとして、アプリケーションがソケットを閉じ、TCPスタックが即座にリセットを送信するか、FINパケットに対する確認応答が得られなかった後にリセットを送信する場合が挙げられます。これは最初のシナリオと似ていますが、クライアントのTCPスタックは、単にソケットを閉じるのではなく、ソケットを解放する前にリセットを送信します。 繰り返しになりますが、適切に記述されたクライアントアプリケーションはこれをエラーとして認識しますが、あまり適切に記述されていないアプリケーションはエラーとして認識しません。
リセット信号を受信すると、サーバーソケットは切断され、すべてのデータが破棄されるため、サーバーアプリケーションは接続が確立されたという兆候すら認識できず、ただ次の接続を待ち続ける状態になります。
リセットを送信
リセットを行うと、ソケットがバックログキューから事実上削除されるため、accept関数はそのソケットを認識せず、別の接続要求を待機することになります。
では、これらすべては実際には何を意味するのでしょうか?まず、クライアントアプリケーションは接続応答が早くなる可能性がありますが、アプリケーションレベルのバナーやメッセージが表示されるまで、より長く待たなければならない場合があります。このバナーやメッセージに関するタイムアウト時間は、長めに設定し直す必要があるかもしれません。次に、サーバーアプリケーションは、acceptを実行した直後にrecv呼び出しからエラーが発生する可能性に備えておく必要があります。これは以前からあり得ることでしたが、その発生確率は高まっています。 最後に、サーバーにデータを送信し、何らかの応答メッセージを期待していないクライアントアプリケーションについては、サーバーアプリケーションが接続を正しくシャットダウンしたことを確認するように実装する必要があります。そうしないと、知らず知らずのうちにデータを失うリスクがあります。繰り返しになりますが、この可能性は以前から存在しており、STCP特有のものではありませんが、その発生確率は現在、わずかに高くなっています。
