アプリケーションが send を2回呼び出しても、受信側は一度にすべて読み取ることもあれば、複数回に分けて読み取ることもあります。TCP が保証するのは順序付きバイトストリームであり、アプリケーションが書き込んだメッセージの境界は保持されません。これを理解するには、1つのデータフローを追跡します。シーケンス番号はバイトを識別し、ACK は受信の進捗を示し、ウィンドウは送信可能な範囲を制約し、パケット損失の検出は回復をトリガーし、輻輳制御はネットワークにどの程度の負荷をかけるかを決定します。
すぐに特定したいこと この記事の重点
「パケットの粘着」、短読み、ACK の意味 バイトストリームとアプリケーションへの配信
SYN/FIN がなぜ ACK をインクリメントするか シーケンス空間と接続のライフサイクル
後のデータを受信してもなぜ重複 ACK が送られるか 累積 ACK、SACK、ギャップ
帯域が広くても送信が遅い理由 rwnd、cwnd、在途量と BDP
再送はいつ発生するか 重複 ACK、RACK-TLP、RTO
CUBIC と BBR の理解方法 カーブの計算例、モデル、バージョンの境界
バイトストリーム:信頼性の高い受信=ビジネス完了ではない
TCP は接続が維持可能な場合に、確認応答、再送、順序付き配信によってデータを回復しますが、永久にネットワークが切断された場合でも最終的な成功を保証するものではありません。send の成功は通常、データがローカルの送信パスに入ったことを示します。TCP ACK は、相手側 TCP が対応するバイトを受信したことを意味しますが、相手側アプリケーションがそれを読み取り、ディスクに書き込み、トランザクションをコミットしたことを証明するものではありません。ビジネスレベルの確認は、依然としてアプリケーションプロトコルによって表現されるべきです。
例えば、abc と def の2回の書き込みに対して、受信側は ab と cdef を読み取る可能性があります。メッセージの境界を定義するのは、長さプレフィックス、固定長、または区切り文字などのフレーミングです。PSH は信頼性の高いメッセージ区切りフラグではありません。また、TCP には暗号化やサーバーの身元認証の機能は組み込まれておらず、TLS などのメカニズムが別の役割を担います。RFC 9293:TCP 基本仕様
ヘッダーフィールドがどのように動作に結びつくか
フィールド ビット数/単位 実際の意味
ソース/宛先ポート 各 16 bit アドレス、プロトコル、ネットワークコンテキストと組み合わせてエンドポイントを特定
Sequence Number 32 bit 現在のセグメントの開始シーケンス番号。SYN を含む場合は SYN の占位を考慮
Acknowledgment Number 32 bit ACK が有効な場合、次に期待される連続したシーケンス番号
Data Offset 4 bit、単位 4 B TCP ヘッダーの長さ。通常 20〜60 B
コントロールフラグ SYN、ACK、FIN、RST、PSH、ECE、CWR など 接続確立、確認応答、クローズ、リセット、輻輳通知など
Window 16 bit、交渉によりスケーラブル 相手側が通知する受信ウィンドウ。ソケットメモリ全体がそのまま公開されるわけではない
Checksum 16 bit 擬似ヘッダー、TCP ヘッダー、データをカバー。暗号学的認証ではない
Urgent Pointer 16 bit URG と組み合わせた歴史的な緊急データセマンティクス。独立した優先チャネルとして使用することはできない
TCP の長さは、IP レベルの長さと実際のヘッダーチェーンから導き出す必要があり、Data Offset をそのままセグメントの長さとして使用してはいけません。32 bit のシーケンス番号はラップアラウンドしますが、比較はプロトコルのモジュラシーケンス番号ルールとウィンドウ制約に従って処理する必要があり、通常の整数の大小比較ではすべての状態をカバーできません。
オプション:一部は接続確立時のみ交渉される
オプション 役割 境界
MSS、Kind 2 自側が受信可能な TCP データセグメントの上限を通知 SYN のみで出現。TCP/IP ヘッダーは含まない
Window Scale、Kind 3 自側の今後のウィンドウフィールドのスケーリングビット数。最大 14 SYN で交渉。SYN 自身のウィンドウフィールドはスケーリングされない
SACK Permitted、Kind 4 相手側が SACK を使用できることを示す 実際の SACK 範囲レポートとは分離
SACK、Kind 5 非連続な受信範囲をレポート 累積 ACK を再定義するものではない
Timestamp、Kind 8 RTT 測定や PAWS などをサポート 交渉と仕様に従って使用する必要があり、すべての TCP パケットにデフォルトで含まれるわけではない
ウィンドウスケーリングは双方向です。C が通知する shift は、C が今後送信する受信ウィンドウを解釈するために使用され、S から C への送信を制限します。MSS もまた、相手側から自側へのデータ送信を制約し、実際の送信はさらに PMTU、オプション、輻輳、受信ウィンドウの制約を受けます。RFC 7323:スケーリングとタイムスタンプ
ハンドシェイク:シーケンス番号の確認と接続状態
ハンドシェイクで両方向のシーケンス番号を同期;データ確認はバイト単位で累積
クライアント C
サーバー S
SYN:seq=1000
SYN + ACK:seq=5000、ack=1001
ACK:seq=1001、ack=5001
100 B データ:seq=1001、ack=5001
ACK:seq=5001、ack=1101
SYN は1つのシーケンス番号を占有します。100 B のデータは [1001,1101) をカバーします。純粋な ACK はシーケンス番号を 占有せず、両方向に独立したシーケンス空間があります。
クライアントの SYN はシーケンス番号 1000 を使用し、サーバーは ACK=1001 でそれを確認し、自身の ISN=5000 を提示します。クライアントはさらに 5001 を確認します。3番目のステップにより、サーバーは自身のシーケンス番号が相手側によって確認されたことを知り、古い重複する接続試行を排除するのに役立ちます。ハンドシェイクで確認されるのは当時のプロトコル状態であり、その後のネットワークが常に到達可能であることを保証するものではありません。
SYN と FIN はそれぞれ1つのシーケンス番号を占有し、制御イベントを順序付きの確認と再送に統一して組み込むことができます。これは「シーケンス番号を占有しないメッセージはどのプロトコルでも再送できない」と逆推定してはなりません。純粋な ACK はシーケンス番号を占有せず、また、各 ACK ごとに別途確認応答を行うこともありません。
通常のクライアントは SYN-SENT を経て、サーバーは SYN-RECEIVED を経て、その後 ESTABLISHED に入ります。同時オープン、データを含む拡張、および異常なリセットには別のルールがあります。SYN cookies は、一部の半接続リソース圧力に対する抵抗のための実装メカニズムであり、交渉能力と使用条件があり、通常のキュー計画やすべての保護策を置き換えるものではありません。RFC 9293:接続確立
累積 ACK、SACK、再送範囲
累積 ACK はギャップで停止し、SACK は別途受信済みの範囲をレポート
[1001,2001)
連続して受信済み
[2001,3001)
ギャップ
[3001,4001)
受信済みだが、順序付き配信はまだ不可
累積 ACK = 2001
SACK = [3001,4001)
SACK の右端は範囲に含まれません。中間の 1000 B を埋めると、累積 ACK は 4001 まで進みます。
[1001,2001) と [3001,4001) を受信し、中間の [2001,3001) が損失したと仮定します。累積 ACK は 2001 に留まります。SACK は別途 [3001,4001) をレポートします。ギャップを埋めると、連続範囲は 4001 まで伸び、ACK がそこにジャンプできるようになります。
SACK は受信事実を表現しますが、送信側はスコアボードを維持し、損失を判断して再送を手配する必要があります。「SACK を有効にすれば、常に本当に損失した1つのデータのみを再送する」というのは極端な言い方であり、順不同や誤判定が回復に影響を与える可能性があります。受信側にも SACK 済みデータの取り消し境界があるため、送信側は SACK があるからといって、累積確認の前にすべての再送コピーを破棄することはできません。RFC 2018:SACK
損失検出と輻輳制御は関連していますが異なる問題です。前者はどのデータが損失した可能性があるかを決定し、後者は次に許可される負荷を決定します。古典的な高速再送は、重複 ACK などのシグナルを利用します。現代の RACK は、送信時間と後続の確認に基づいて損失を推測し、TLP は末尾のプローブによって一部の末尾損失回復を改善し、3回の重複 ACK を待つ必要はありません。RFC 8985:RACK-TLP
RTO がなぜ RTT に直接等しいわけではないのか
RTO は遅延の変動に対する余裕を残す必要があります。RFC 6298 の基本推定は SRTT + max(G, 4×RTTVAR) であり、最小値とバックオフ規則が組み合わされています。最初のサンプル R=100 ms の場合、SRTT=100 ms、RTTVAR=50 ms で、元の計算値は 300 ms です。この RFC で推奨される 1 s の下限により 1 s となり、実際のコアの実装の下限は別途照合する必要があります。
次のサンプルが 120 ms の場合、まず古い SRTT を使用して RTTVAR を更新します:0.75×50 + 0.25×20 = 42.5 ms。次に SRTT を更新します:0.875×100 + 0.125×120 = 102.5 ms。元の RTO は 272.5 ms です。再送後の ACK は、どの送信を確認したのかを特定できない場合があります。Karn ルールとタイムスタンプは、対応するサンプリングの曖昧さを処理するために使用されます。タイムアウトもバックオフをトリガーします。RFC 6298:再送タイマー
ACK 到達点とアプリの読取位置
第三、第一の順に送り、連続部分だけ ACK が進むことを見る。第二を補うと二段分進める。その後に読み、確認と消費を分ける。表示 SACK の隣接区間は統合可能。
図を準備しています 再読み込み
ACK 到達点とアプリの読取位置 累積 ACK は次の期待バイト。順不同データは保留できるが、穴を飛ばしてアプリへ渡せない。読取で ACK の意味は変わらない。
{"id":"tcp-receive-holes","title":"ACK 到達点とアプリの読取位置","summary":"累積 ACK は次の期待バイト。順不同データは保留できるが、穴を飛ばしてアプリへ渡せない。読取で ACK の意味は変わらない。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"one\" data-i18n=\"one\"></button><button id=\"two\" data-i18n=\"two\"></button><button id=\"three\" data-i18n=\"three\"></button><button id=\"read\" data-i18n=\"read\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><section class=\"panel\"><h3 data-i18n=\"buffer\"></h3><div id=\"buffer\"></div></section><div class=\"metrics\"><div><span data-i18n=\"ack\"></span><strong id=\"ack\"></strong></div><div><span data-i18n=\"app\"></span><strong id=\"app\"></strong></div></div><p id=\"sack\" class=\"message\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n#buffer{display:flex;gap:6px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let got=new Set,app=1001;function draw(){let n=0;while(got.has(n))n++;const ack=1001+n*1000;cells('buffer',[0,1,2].map(i=>({text:'['+(1001+i*1000)+','+(2001+i*1000)+')'+(app>1001+i*1000?' ✓':''),cls:!got.has(i)?'empty':i<n?'on':'gold'})));set('ack',ack);set('app',app);const blocks=[...got].filter(i=>i>=n).sort().map(i=>'['+(1001+i*1000)+','+(2001+i*1000)+')');set('sack','SACK ranges: '+(blocks.join(', ')||'∅')+'\\nreadable = '+(ack-app)+' B');$('#read').disabled=ack===app;$('#ack').dataset.value=ack;$('#app').dataset.value=app;}['one','two','three'].forEach((id,i)=>$('#'+id).onclick=()=>{got.add(i);draw()});$('#read').onclick=()=>{app=+$('#ack').textContent;draw()};$('#reset').onclick=()=>{got=new Set;app=1001;draw()};draw();","audio":false,"strings":{"ack":"累積 ACK","app":"次のアプリ読取位置","buffer":"バッファ:青は連続、金は順不同","heading":"ACK 到達点とアプリの読取位置","intro":"任意順に区間を届け、連続部分を読む。金色は穴の後ろの保留データ。","one":"[1001,2001) 配送","read":"アプリ読取","reset":"リセット","scope":"1001から1000 Bの三区間。周回・受信容量・FINは省略。読取は連続データ消費で、業務確定ではない。","scopeLabel":"モデルの範囲と前提","three":"[3001,4001) 配送","two":"[2001,3001) 配送"}}
フロー制御と輻輳ウィンドウ:在途量をまず差し引く
通常の、回復しない簡易モデルでは、送信側は受信ウィンドウ rwnd と輻輳ウィンドウ cwnd の両方に制限されます。min(rwnd, cwnd) は在途規模の制約であり、毎回これだけを送信できるわけではありません。さらに、送信済みで未確認のデータを差し引き、パシング、アプリケーションの利用可能なデータ、実際の実装を考慮する必要があります。
SND.UNA=1001、SND.NXT=4001 と設定すると、すでに 3000 B が未確認です。rwnd=8000 B、cwnd=5000 B の場合、簡易的な新データの余力は min(8000, 5000) - 3000 = 2000 B です。cwnd を 20000 B に引き上げると、制約は受信ウィンドウになり、新余力は 5000 B になります。
制限の要因 典型的な証拠 もう一方を増やしても解決するか
受信側の読み取りが遅く、rwnd が小さい 通知ウィンドウの縮小、ゼロウィンドウ cwnd のみの増設は無効
ネットワーク輻輳または回復、cwnd が小さい 輻輳イベント、ウィンドウ変化、再送 受信バッファのみの増設は無効
アプリケーションのデータ供給不足 送信キューの空、アプリケーションの停止 いずれかのウィンドウを増やしても無効な場合あり
パシングまたはホスト処理能力 送信リズム、CPU/キューの証拠 ウィンドウサイズのみでは説明できない
ゼロウィンドウ後、送信側の persist メカニズムはウィンドウが再び開かれたかどうかをプローブし、ウィンドウ更新の損失による長期停止を防ぎます。これはアイドル接続の keepalive やパケット損失再送タイマーとは異なる用途です。Nagle は、未確認データが存在する場合の小セグメント送信を制限し、遅延 ACK と組み合わされると、小規模なインタラクションの待ち時間が長くなる可能性があります。TCP_NODELAY はアプリケーションのメッセージ境界を回復させるものではありません。
帯域幅遅延積(BDP)の再計算
1 Gbit/s、RTT=50 ms の BDP は 10⁹/8 × 0.05 = 6,250,000 B です。スケーリングされていない 65535 B のウィンドウでは、他の制限を無視しても、ウィンドウ/RTT が与える上限は約 10.486 Mbit/s に過ぎません。scale=7 の場合、最大で 8,388,480 B(約 8 MiB)まで表現でき、ようやくこの BDP をカバーできます。これは必要な予算ですが、スループットの保証ではありません。
ウィンドウと残送信量は違う
三つは別の状態量。飛行中量を二つの窓の間に置くと、大きい制約で小さい制約を補えない。窓を飛行中量以下に縮めても既送信量は残り、新規送信が止まる。
cre d i t = max ( 0 , min ( r w n d , c w n d ) − Fl i g h tS i ze )
図を準備しています 再読み込み
ウィンドウと残送信量は違う 小さい制約から飛行中量を引く。上限が飛行中量より小さければ新規枠はゼロで、既送信量は負にならない。
{"id":"tcp-window-budget","title":"ウィンドウと残送信量は違う","summary":"小さい制約から飛行中量を引く。上限が飛行中量より小さければ新規枠はゼロで、既送信量は負にならない。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"rwnd\" data-i18n=\"rwnd\"></label><output for=\"rwnd\" id=\"rwnd-value\"></output><input id=\"rwnd\" type=\"range\" min=\"0\" max=\"10000\" value=\"8000\" step=\"500\"></div><div class=\"control\"><label for=\"cwnd\" data-i18n=\"cwnd\"></label><output for=\"cwnd\" id=\"cwnd-value\"></output><input id=\"cwnd\" type=\"range\" min=\"0\" max=\"10000\" value=\"5000\" step=\"500\"></div><div class=\"control\"><label for=\"flight\" data-i18n=\"flight\"></label><output for=\"flight\" id=\"flight-value\"></output><input id=\"flight\" type=\"range\" min=\"0\" max=\"10000\" value=\"3000\" step=\"500\"></div><div id=\"limits\" class=\"intervals\"></div><div class=\"metrics\"><div><span data-i18n=\"credit\"></span><strong id=\"credit\"></strong></div></div><p id=\"equation\" class=\"formula\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function draw(){const r=+$('#rwnd').value,c=+$('#cwnd').value,f=+$('#flight').value,limit=Math.min(r,c),credit=Math.max(0,limit-f);for(const id of ['rwnd','cwnd','flight'])set(id+'-value',$('#'+id).value);intervals('limits',[{start:0,end:r,label:'rwnd '+r},{start:0,end:c,label:'cwnd '+c},{start:0,end:f,label:'FlightSize '+f,cls:'gold'},{start:Math.min(f,limit),end:limit,label:'credit '+credit}],10000);set('credit',credit);set('equation','SND.NXT = 1001 + '+f+' = '+(1001+f)+'\\ncredit = max(0, min('+r+', '+c+') − '+f+') = '+credit);$('#credit').dataset.value=credit;}document.querySelectorAll('input').forEach(e=>e.oninput=draw);draw();","audio":false,"strings":{"credit":"新規送信 / B","cwnd":"cwnd(B)","flight":"FlightSize(B)","heading":"ウィンドウと残送信量は違う","intro":"受信・輻輳ウィンドウと飛行中量を変え、上限と残量を比較。","reset":"リセット","rwnd":"rwnd(B)","scope":"SND.UNA=1001、FlightSize近似。SACK pipe・プローブ・ペーシング・回復・更新の妥当性なし。各設定は独立スナップショット。","scopeLabel":"モデルの範囲と前提"}}
クローズ:両方向が独立して終了する
A が FIN を送信した後、新しいアプリケーションデータは送信しませんが、P のデータは依然として受信できます。P は確認応答後、CLOSE-WAIT に留まる可能性があります。これはアプリケーションが自身の送信方向を終了するまで続きます。その後、P は FIN を送信し、A が確認します。ACK と FIN は結合できるため、クローズは必ずしも正確に4つのパケットで行われるわけではありません。同時クローズも状態遷移パスを変更します。
典型的なアクティブクローズ側は FIN-WAIT-1、FIN-WAIT-2、TIME-WAIT を経ます。パッシブ側は CLOSE-WAIT、LAST-ACK を経ます。大量の CLOSE-WAIT が発生している場合は、TIME_WAIT パラメータで説明しようとするのではなく、アプリケーションがなぜまだ接続を終了していないかを確認する必要があります。
TIME-WAIT は、再送された FIN への応答と、古い接続のパケットが新しい接続に干渉するのを防ぐために時間を確保するもので、最後の ACK が必ず届くことを保証するものではありません。仕様では 2MSL で記述されていますが、Linux 7.2 の TCP_TIMEWAIT_LEN は 60×HZ で、60 s に相当します。仕様のモデルと実装のタイマーは区別します。tcp_fin_timeout は対応する孤児状態の FIN-WAIT-2 に対してのものであり、TIME-WAIT の長さスイッチではありません。
接続の再利用は、接続確立/クローズの頻度を削減できます。SO_REUSEADDR は条件を満たすバインディングに影響しますが、すべての4元組の競合を解消するものではありません。tcp_tw_reuse にもセキュリティ条件とバージョン設定の意味があり、パラメータを議論する前に、まず状態とリソースのボトルネックを区別する必要があります。RFC 9293:クローズ 、Linux TCP sysctl
CUBIC:保持率と成長曲線の関係
ネットワークは、パケット損失や ECN などのシグナルによって輻輳を反映させることができます。輻輳制御が「推測」のみで、ネットワークが能動的にフィードバックしないとは言えません。CUBIC の中核的な目標カーブは以下の通りです。
W(t) = C×(t−K)³ + Wmax
β が輻輳後の保持比率を表すとき、K = ∛(Wmax×(1−β)/C) です。β=0.7、C=0.4、Wmax=100 セグメントを取ると、K≈4.22 s になります。W(0)=70 セグメント、W(K)=100 セグメントです。古い公式は、保持比率 0.7 を K の分子に直接入れるため、W(0) が 30 セグメントと誤算されます。RFC 9438:CUBIC
CUBIC 教育用カーブ:β=0.7、Wmax=100 セグメント、C=0.4
セグメント
t / s
70
85
100
115
W(0)=70
W(K)=100、K≈4.22 s
ウィンドウの単位は SMSS セグメント、時間の単位は秒です。これは目標カーブであり、完全な実装はさらに ACK、回復、R eno 友好領域と組み合わされています。
カーブは古い Wmax に近づくと成長が鈍化し、それを越えると探査を続けます。時間はこの関数の独立変数ですが、実際の TCP は依然として ACK、RTT、Reno 友好領域、回復ルールに依存しており、「スループットが RTT の影響を全く受けない」と要約することはできません。起動時の近似指数成長も、ACK の動作と制限条件の影響を受けるため、すべての接続で毎 RTT 正確に2倍になるとは限りません。
CUBIC の輻輳応答は、個別のパケット損失ごとに独立して 0.7 倍になることを意味しません。イベントのマージ、高速回復、タイムアウトには異なる処理があります。キューサイズ、AQM、ECN、ランダムなパケット損失、競合フローは、最終的なレイテンシとスループットに影響を与えます。
変曲点前後で増加が遅くなり再加速する理由
K を通ると窓は増え続け、傾きはゼロまで下がって再上昇する。β 変更で初期窓と K が動いても W(K) は旧 Wmax に戻る。平行移動項の役割を確かめる。
W c u bi c ( t ) = C ( t − K ) 3 + W ma x , K = 3 W ma x ( 1 − β ) / C
図を準備しています 再読み込み
変曲点前後で増加が遅くなり再加速する理由 K は旧 Wmax 付近に平坦域を置く。β は保持割合で削減割合ではない。三次目標は実送信レートではない。
{"id":"cubic-window-shape","title":"変曲点前後で増加が遅くなり再加速する理由","summary":"K は旧 Wmax 付近に平坦域を置く。β は保持割合で削減割合ではない。三次目標は実送信レートではない。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"time\" data-i18n=\"time\"></label><output for=\"time\" id=\"time-value\"></output><input id=\"time\" type=\"range\" min=\"0\" max=\"10\" value=\"0\" step=\"0.1\"></div><div class=\"control\"><label for=\"beta\" data-i18n=\"beta\"></label><output for=\"beta\" id=\"beta-value\"></output><input id=\"beta\" type=\"range\" min=\"0.5\" max=\"0.9\" value=\"0.7\" step=\"0.05\"></div><svg id=\"curve\" class=\"diagram\" viewBox=\"0 0 360 180\" role=\"img\" data-i18n-label=\"heading\"></svg><div class=\"metrics\"><div><span data-i18n=\"window\"></span><strong id=\"window\"></strong></div><div><span data-i18n=\"slope\"></span><strong id=\"slope\"></strong></div></div><p id=\"equation\" class=\"formula\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function draw(){const time=+$('#time').value,beta=+$('#beta').value,K=Math.cbrt(100*(1-beta)/.4),w=t=>.4*(t-K)**3+100,value=w(time),slope=1.2*(time-K)**2;set('time-value',time.toFixed(1));set('beta-value',beta.toFixed(2));const x=t=>30+t*30,y=w=>160-w*.45,points=Array.from({length:101},(_,i)=>x(i/10)+','+y(w(i/10))).join(' ');$('#curve').innerHTML='<path class=\"edge\" d=\"M30 15 V160 H340\"/><line class=\"edge\" x1=\"30\" y1=\"'+y(100)+'\" x2=\"330\" y2=\"'+y(100)+'\"/><polyline fill=\"none\" stroke=\"var(--accent)\" stroke-width=\"3\" points=\"'+points+'\"/><circle cx=\"'+x(time)+'\" cy=\"'+y(value)+'\" r=\"5\" fill=\"var(--second)\"/><text x=\"34\" y=\"'+(y(100)-7)+'\">Wmax=100</text><text x=\"280\" y=\"178\">t (s)</text><text x=\"'+x(K)+'\" y=\"155\">K</text>';set('window',value.toFixed(2));set('slope',slope.toFixed(2));set('equation','K = ∛(100×(1−'+beta.toFixed(2)+')/0.4) = '+K.toFixed(3)+' s\\nW(0) = '+(100*beta).toFixed(1)+'; W(K) = 100\\nW′(t) = 1.2(t−K)²');$('#window').dataset.value=value;}document.querySelectorAll('input').forEach(e=>e.oninput=draw);draw();","audio":false,"strings":{"beta":"保持率 β","heading":"変曲点前後で増加が遅くなり再加速する理由","intro":"時刻と保持率を動かし、曲線と傾きを見る。","reset":"リセット","scope":"Wmax100セグメント、C0.4。目標と導関数のみ。Reno互換域・ACK更新・アプリ制限・再損失なし。","scopeLabel":"モデルの範囲と前提","slope":"現在の傾き(段/s)","time":"損失からの時間(s)","window":"目標窓(セグメント)"}}
BBR:帯域幅と遅延モデルの検証も必要
BBR は、配信レートや RTT などの観測値を使用してパス容量を推定し、パシングと在途制約を組み合わせて送信を制御します。典型的なプロセスには、容量の高速探査、起動時に形成されたキューの排空、帯域幅の周期的な探査、最小遅延推定の更新が含まれます。観測値は ACK の集約、アプリケーションによる制限、パスの変化、他のフローの影響を受けるため、モデルは実際の物理的なボトルネックを直接読み取るものではありません。
Linux 7.2 の tcp_bbr.c には、古典的な4つのモード、約 2.885 の起動時パシングゲイン、および 1.25/0.75 などの定常状態ゲインが含まれています。これらはすべての BBR バージョンにおける統一定数として一般化することはできません。帯域幅推定、cwnd ゲイン、パシングゲインは異なり、在途量を常に正確に1つの BDP に固定することはできません。
Google の BBR プロジェクトには、継続的に進化している実装と資料が含まれており、比較するバージョンを明確にする必要があります。RFC 9438 は CUBIC を定義します。BBR のアルゴリズムとバージョンは対応するプロジェクト資料で確認します。「BBR はパケット損失によって決して減速しない」「必ずキューがない」「限定のないデプロイメント割合」などの実験条件を代替しないでください。Google BBR プロジェクト
比較次元 CUBIC BBR シリーズ
中核的な理解の入り口 ウィンドウ成長カーブと輻輳応答 配信レート/遅延モデルとパシング
必須の説明条件 回復アルゴリズム、ECN/AQM、RTT と負荷 バージョン、観測バイアス、パケット損失/ECN 処理
保証されないこと 同等の帯域幅下で永遠に公平または低レイテンシ 常にキューがない、すべての競合フローに対して公平
検証指標 スループット、RTT 分布、再送、キューと公平性 これらの指標すべてが必要であり、単一フローのピークのみを見るものではない
ローカル推論:バイト範囲、ウィンドウ、カーブ
import math
received = [ ( 1001 , 2001 ) , ( 3001 , 4001 ) ]
def cumulative ( intervals , start = 1001 ) :
ack = start
for left , right in sorted ( intervals ) :
if left > ack :
break
ack = max ( ack , right )
return ack
assert cumulative ( received ) == 2001
assert cumulative ( received + [ ( 2001 , 3001 ) ] ) == 4001
assert min ( 8000 , 5000 ) - ( 4001 - 1001 ) == 2000
assert 1_000_000_000 / 8 * . 05 == 6_250_000
assert 65535 < < 7 == 8_388_480
assert math . isclose ( 65535 * 8 / . 05/ 1e6 , 10. 4856 )
srtt ,variation = . 1 ,. 05
sample = . 12
variation = . 75* variation + . 25* abs ( srtt - sample )
srtt = . 875* srtt + . 125* sample
assert math . isclose ( srtt + 4 * variation , . 2725)
beta ,C ,Wmax = . 7 ,. 4 ,100
K = ( Wmax * ( 1 - beta ) / C ) * * ( 1 / 3 )
def target ( t ) :
return C * ( t - K ) * * 3 + Wmax
assert math . isclose ( target ( 0 ) , 70 )
assert math . isclose ( target ( K ) , 100 )
assert target ( K + 1 ) > 100
print ( ' ACK ギャップ、送信余力、BDP、RTO、CUBIC 境界チェック通過' )
これは局所的な数学モデルであり、実際の再送タイマー、コアの輻輳状態、またはネットワークの公平性をシミュレートするものではありません。輻輳制御と接続パラメータは変更されていません。
さらに調べる