📶

HTTP Keep-Aliveの挙動をtcpdumpで見てみた

HTTP Keep-Aliveの挙動をtcpdumpで見てみた

HTTP Keep-Aliveとは

「HTTP Keep-Alive」という言葉をご存じでしょうか。
Webアプリケーションの開発をした方なら一度は耳にしたことがあると思います。ただ、それが何かを説明するのはなかなか難しいかもしれません。
Wikipediaを見るとHTTPの持続的接続という項目で以下のように記載されています。

HTTPの持続的接続(HTTPのじぞくてきせつぞく)は、同じTCP接続を使い、複数のHTTPリクエスト・レスポンスを送受信するアイデアである。
クライアントは一回のTCP接続で複数のコンテンツを要求することにより、通信パフォーマンスを向上させる事が出来る。

この説明だけを見て動作を理解することは難しいと思いますので、分かりやすいように図解してみます。
例として、/test.htmlにリクエストした後に続けて/test.jsにリクエストするようなページがあった場合、Keep-Aliveが無効な場合と有効な場合でおそらく以下のような通信が行われていると想像できます。

Keep-Aliveが無効な場合:

図1:Keep-Alive無効

Keep-Aliveが有効な場合:

図2:Keep-Alive有効

Keep-Aliveが有効な場合のほうが明らかに通信を示す矢印の数が少なく、通信が効率的に行われていることが分かります。
実際にこのように通信が行われているのでしょうか?tcpdumpでパケットを見ることにより確認できますので実際に動かして確認してみましょう。

HTTP Keep-Aliveの挙動を確認する

今回はVirtualBox上に以下の環境を用意して実験してみます。

  • Ubuntu 22.04
  • nginx (sudo apt install nginxコマンドでインストール)
  • tcpdump (デフォルトでインストール済み)
  • Firefox (デフォルトでインストール済み)

nginxをインストールすると設定ファイル/etc/nginx/nginx.confが生成されるので
keepalive_timeout 0;と記載すればKeep-Aliveは無効、
keepalive_timeout 10;のように記載すればKeep-Aliveは有効でタイムアウト10秒、ということになります。
※設定ファイルを変更したらsudo systemctl restart nginxで再起動して変更を反映します。

また、Firefoxでhttp://127.0.0.1/(nginxのデフォルトページ)にアクセスすると/favicon.icoにもリクエストが行われるため、連続して2回リクエストが行われることからKeep-Aliveの動作を確認するのにちょうどいい環境となります。(favicon.icoは404になりますが)

図3:Firefoxでアクセス

ではUbuntuのターミナルでtcpdumpを実行した状態でFirefoxからnginxへリクエストを行い実際に通信の様子を見てみましょう。
sudo tcpdump -i lo -nn port 80としてローカルホストの80番ポートを監視します。

Keep-Aliveが無効な場合:

図4:Keep-Alive無効

Keep-Aliveが有効な場合:

図5:Keep-Alive有効(10秒)

通信内容を見ると想像したとおり、Keep-Alive無効の場合はHTTPリクエストのたびに接続を開いて毎回閉じている(クライアント側のポート番号も変わる)のに対し、Keep-Alive有効の場合は一度開いた接続を閉じずに使いまわしていることが分かります。
Flags [S]がSYN、Flags [F]がFINを指します。

クライアントから通信を切断するか、サーバから通信を切断するか

先ほどの実験では、nginx側つまりHTTPサーバ側でKeep-Aliveタイムアウト時間を10秒に設定して行いました。
Keep-Aliveタイムアウト時間はサーバ側にしか設定できないのでしょうか?
実際にはクライアント側でも設定可能です。
Firefoxの場合、about:configで設定画面を開きnetwork.http.Keep-Alive.timeoutの値を変更することで設定できます。(デフォルト115秒)

図6:Firefoxの設定の例

では、タイムアウト時間がクライアント側のほうが短い場合とサーバ側のほうが短い場合で挙動の違いを見てみましょう。
Firefox側でタイムアウト時間5秒と20秒に設定し、同じようにhttp://127.0.0.1/にアクセスして通信内容をtcpdumpで見てみます。

クライアント側のほうが短い場合(サーバ10秒>クライアント5秒):

図7:クライアントのほうが短い場合

サーバ側のほうが短い場合(サーバ10秒<クライアント20秒):

図8:サーバのほうが短い場合

設定したとおり、タイムアウト時間の設定が短い方からFINパケットが送出され接続を切断するトリガになってることが分かりました。

サーバから切断すると何が起こるか

Keep-Aliveタイムアウト時間はサーバ側とクライアント側双方で好きなように設定すればいいのでしょうか?
実は、サーバ側のほうが短い場合、つまりサーバから通信を切断する場合はタイミングによって問題が起こりえます。
具体的には以下のように、通信の切断とリクエストが同時に送信されてしまうケースです。

図9:衝突する場合

HTTPリクエストはクライアント側から発行されるため、クライアント側から接続を切断するようになっていれば問題はありませんが
サーバ側から通信を切断する場合、通信が行き違いになってエラーが発生してしまうケースが出てきてしまいます。

実際にCDNベンダーであるAkamaiのサポート情報でも、オリジン(HTTPサーバ側)のタイムアウトをAkamaiサーバ(HTTPクライアント側)よりも長くすることを推奨しています。

Akamai のデフォルト pconn タイムアウト設定は5分です。オリジンの pconn タイムアウト設定はそれよりわずかに大きい値 (例: 5分と1-2秒 [301-302秒]) に設定することを推奨しております。
オリジンの pconn タイムアウト値が 5分未満の場合、まれにエラーが発生することがあります。この場合、オリジンは Akamai サーバが既存の pconn でリクエストを送信していると同時に pconn を終了しようとするかもしれません。

またAWSのApplication Load Balancerの説明でも同様にアプリケーション(HTTPサーバ側)のタイムアウトをロードバランサ(HTTPクライアント側)より長くすることを推奨してます。

HTTP キープアライブを有効にすると、ロードバランサーはキープアライブのタイムアウト期間が終了するまで、バックエンド接続を再利用できます。また、アプリケーションのアイドルタイムアウトは、ロードバランサーに設定されたアイドルタイムアウトよりも大きな値に設定することをお勧めします。そうしないと、アプリケーションがロードバランサーへの TCP 接続を正常に閉じない場合、ロードバランサーは、接続が閉じられたことを示すパケットを受信する前に、アプリケーションにリクエストを送信することがあります。この場合、ロードバランサーは HTTP 502 Bad Gateway エラーをクライアントに送信します。

HTTP Keep-Aliveの動作原理を理解していれば、これらの説明もすんなり理解できるのではないでしょうか。

まとめ

Webアプリケーションを普段開発している方でも、HTTP Keep-Aliveの動作をあまり意識することはないかもしれません。
またtcpdumpのような低レイヤーのツールを使う機会もあまりないかもしれません。
ただ、実際にパケットのやり取りを見ることで分かることもあるのではないでしょうか。
この記事がtcpdumpを使ってみるきっかけになったら幸いです。

この記事を書いた人

樫木 淳
2019年中途入社 旅行プラットフォーム部エンジニア。
旅行会社向けアプリケーションの運用・保守を担当。
上の子はもう中学生。

FORCIA Tech Blog

Discussion