TCPプロトコルを使用してサーバークライアントネットワークトポロジで信頼性の高い通信を確保するオンライン(グリッドベース)ビデオゲームを出荷しました。私のゲームはかなりうまく機能しますが、予想よりも高い遅延が発生します(同様のTCPこのジャンルのゲームは、待ち時間を最小限に抑えるのにより良い仕事をしているようです)。
調査中に、Microsoft Windowsを実行しているクライアント(Mac OS Xクライアントとは対照的に)のレイテンシが予想外に高いことを発見しました。さらに、Windowsクライアントが設定した場合 TcpAckFrequency=1
レジストリ内 そしてマシンを再起動すると、レイテンシーは正常になります。
私のネットワーク設計では、確認応答の遅延が考慮されていなかったようです。
遅延確認応答、Nagleアルゴリズム、およびWinsockバッファリングの相互作用を考慮しない設計は、パフォーマンスに大幅な影響を与える可能性があります。 ( http://support.Microsoft.com/kb/214397 )
ただし、自分のゲーム(または任意のゲーム)での遅延承認を考慮することはほぼ不可能だと感じています。 MSDNによると、Microsoft TCPスタックは、次の基準を使用して、受信したデータパケットで1つのACKを送信するタイミングを決定します。
- 遅延タイマーが切れる前(200ms)に2番目のデータパケットが受信されると、ACKが送信されます。
- 2番目のデータパケットが受信される前にACKと同じ方向に送信されるデータがあり、遅延タイマーが期限切れになると、ACKはデータセグメントにピギーバックされ、すぐに送信されます。
- 遅延タイマーが時間切れになると(200ms)、ACKが送信されます。
これを読むと、MicrosoftのTCPスタックでの遅延確認応答の回避策は次のとおりであると推測されます。
SO_SNDBUF
= 0)であるため、send
への呼び出しはパケットを送信することが期待できます。send
を呼び出すときに、それ以上のデータがすぐに送信されることが予想されない場合は、受信者によって破棄される1バイトのデータを使用してsend
を再度呼び出します。このアプローチでは、2番目のデータパケットは、前のデータパケットとほぼ同時に受信者によって受信されます。その結果、ACK
は受信者から送信者にすぐに送信されるはずです(何をTcpAckFrequency=1
はレジストリにあります)。
ただし、私のテストによると、これによりレイテンシーはレジストリ編集の約半分しか改善されませんでした。何が足りないのですか?
A: TCPを選択したのは、送信するすべてのパケットが到着する(そして順序が整っている)必要があるためです。失われた(または順序が狂った)場合に再送信する価値のないパケットはありません。破棄/順序付けなし、UDPはTCPよりも高速ですか?
Windows Vista以降、TCP_NODELAYオプションは、connect
を呼び出す前、または(サーバー上で)listen
を呼び出す前に設定する必要があります。 connect
を呼び出した後にTCP_NODELAY
を設定した場合、Nagleアルゴリズムは無効になりません実際には Nagleアルゴリズムは無効になりますが、GetSocketOption
はNagleが無効になったことを示します。これはすべて文書化されていないようであり、この主題に関する多くのチュートリアル/記事が教えていることと矛盾しています。
Nagleを実際に無効にすると、TCP確認応答の遅延によって遅延が発生しなくなりました。
あなたがする必要があることは何もないはずです。あなたが提案しているすべての回避策は、TCP上で動作するように適切に設計されていないプロトコルを支援することです。おそらくあなたのプロトコルはTCP上で動作するように設計されていますよね?
あなたの問題はほぼ間違いなくこれらの一方または両方です:
TCP送信関数を小さなデータで呼び出していますが、大きなチャンクで呼び出すことができなかった理由はありません。
アプリケーションプロトコルデータユニットのアプリケーションレベルの確認応答を実装していません。これらを実装して、ACKがそれらに便乗できるようにします。
このアプローチでは、2番目のデータパケットは、前のデータパケットとほぼ同時に受信者によって受信されます。その結果、ACKは受信者から送信者にすぐに送信される必要があります(レジストリでTcpAckFrequency = 1が行うことをエミュレートします)。
これにより、常に2番目の個別のパケットが送信されるとは思いません。 Nagleが無効になっていて、送信バッファーがゼロになっていることは知っていますが、奇妙なことがあります。いくつかのwiresharkダンプが役立つ場合があります。
1つのアイデア:「カナリア」パケットが1バイトだけである代わりに、MSSに相当するデータ全体(通常、1500 MTUネットワークでは1460バイト)を送信します。
この問題を解決するには、TCP接続の通常の機能を理解する必要があります。 Telnetは分析するのに良い例です。
TCPは、データ送信の成功を確認することで配信を保証します。 「Ack」はそれ自体でメッセージとして送信できますが、これによりかなりのオーバーヘッドが発生します。Ackはそれ自体が非常に小さいメッセージですが、下位レベルのプロトコルではヘッダーが追加されます。このため、TCPは、とにかく送信している別のパケットにAckメッセージをピギーバックすることを好みます。 Telnetを介してインタラクティブなシェルを見ると、キーストロークと応答が安定して流れています。また、入力に少し時間がかかる場合は、画面にエコーするものは何もありません。フローが停止する唯一のケースは、対応する入力のない出力がある場合です。ただし、読み取る速度は非常に速いため、数百ミリ秒待って、Ackをピギーバックするキーストロークがあるかどうかを確認しても問題ありません。
つまり、要約すると、双方向のパケットの流れは安定しており、Ackは通常ピギーバックします。アプリケーション上の理由でフローが中断された場合、Ackの遅延は認識されません。
プロトコルに戻る:明らかに、要求/応答プロトコルがありません。つまり、Ackをピギーバックすることはできません(問題1)。そして、受信側のOSは個別のAcksを送信しますが、それらをスパムすることはありません。
TCP_NODELAY
と送信側(Windows)側の2つのパケットを介した回避策は、受信側もWindowsであるか、少なくともそのように動作することを前提としています。 これは希望的観測であり、エンジニアリングではありません。他のOSは、3つのパケットがAckを送信するのを待つことを決定する場合があります。これにより、TCP_NODELAY
の使用が完全に中断されますone余分なパケット。 「3パケット待機」はほんの一例です。 2番目の1バイトのダミーパケットにだまされないAckスパムを防ぐための有効なアルゴリズムは他にもたくさんあります。
本当の解決策は何ですか?プロトコルレベルで応答を送信します。 OSに関係なく、プロトコル応答にTCPAckを便乗させます。同様に、この応答は反対方向にAckを強制します(応答もTCPメッセージです)が、応答の待ち時間は気にしません。応答はそこにあるので、受信OSは最初のAckをピギーバックします。
信頼性の高いUDPライブラリを使用し、独自の輻輳制御アルゴリズムを作成します。これにより、TCP遅延の問題が確実に克服されます。
これは、信頼できるUDP転送に使用する次のライブラリです。