The application calls send twice, but the receiver might read all the data at once or in multiple reads. TCP maintains an ordered byte stream and does not preserve the message boundaries written by the application. Understanding it can be done by tracing a single data transfer: sequence numbers identify bytes, ACKs indicate reception progress, the window constrains the sendable range, loss detection triggers recovery, and congestion control determines how much load to impose on the network.
To quickly locate
Focus of this article
"Packet sticking", short reads, and the meaning of ACK
Byte stream and application delivery
Why SYN/FIN increments the ACK
Sequence number space and connection lifecycle
Why duplicate ACKs are sent when receiving out-of-order data
Cumulative ACK, SACK, and gaps
High bandwidth but slow transmission
rwnd, cwnd, in-flight data, and BDP
When retransmission occurs
Duplicate ACK, RACK-TLP, and RTO
How to understand CUBIC and BBR
Curve examples, models, and version boundaries
Byte Stream: Reliable reception does not equal business completion
TCP recovers data through acknowledgments, retransmissions, and ordered delivery as long as the connection can be maintained; it cannot guarantee eventual success in the event of a permanent network outage. A successful send usually indicates that the data has entered the local send path; a TCP ACK indicates that the peer TCP has received the corresponding bytes, but it does not prove that the peer application has read, written to disk, or committed the transaction. Business confirmation should still be expressed by the application protocol.
For example, with two writes of abc and def, the receiver might read ab and cdef. Framing such as length prefixes, fixed lengths, or delimiters defines message boundaries; PSH is not a reliable message separation flag. TCP does not natively provide encryption or server identity authentication; mechanisms like TLS take on another layer of responsibility. RFC 9293: TCP Basic Specification
How header fields connect to behavior
Field
Bits/Unit
Actual Meaning
Source/Destination Port
16 bits each
Locates endpoints along with address, protocol, and network context
Sequence Number
32 bits
Starting sequence number of the current segment; consider SYN occupying a sequence number when SYN is present
Acknowledgment Number
32 bits
When ACK is valid, the expected next consecutive sequence number
Data Offset
4 bits, unit 4 B
TCP header length, usually 20–60 B
Control Flags
e.g., SYN, ACK, FIN, RST, PSH, ECE, CWR
Connection establishment, acknowledgment, teardown, reset, and congestion feedback, etc.
Window
16 bits, scalable via negotiation
Receive window advertised by the peer; does not expose all socket memory as-is
Checksum
16 bits
Covers pseudo-header, TCP header, and data; not cryptographic authentication
Urgent Pointer
16 bits
Historical urgent data semantics combined with URG; cannot be used as an independent priority channel
TCP length must be derived from the IP layer length and the actual header chain; do not use Data Offset as the length of the entire segment. 32-bit sequence numbers wrap around; comparisons should follow the protocol's modular sequence number rules and window constraints, as standard integer magnitude comparisons cannot cover all states.
Options: Some are only negotiated during connection establishment
Option
Purpose
Boundary
MSS, Kind 2
Advertises the maximum TCP segment size the local side can receive
Only appears in SYN; does not include TCP/IP headers
Window Scale, Kind 3
Number of bits to scale the window field for subsequent windows on this side, max 14
Negotiated in SYN; the window field in the SYN itself is not scaled
SACK Permitted, Kind 4
Indicates that the peer is allowed to use SACK
Separate from actual SACK interval reporting
SACK, Kind 5
Reports non-contiguous received intervals
Does not redefine cumulative ACK
Timestamp, Kind 8
Supports RTT measurement and PAWS, etc.
Must be used according to negotiation and specifications; do not assume all TCP packets naturally carry it
Window scaling is directional: the shift advertised by C is used to interpret the receive window sent by C later, limiting S's transmission to C. MSS also constrains data sent by the peer to the local side, and actual transmission is further limited by PMTU, options, congestion, and receive window. RFC 7323: Window Scale and Timestamps
Handshake: Sequence numbers and connection state
The client's SYN uses sequence number 1000, the server acknowledges it with ACK=1001, and proposes its own ISN=5000; the client then acknowledges 5001. The third step allows the server to know that its sequence number has been acknowledged by the peer, which helps exclude old duplicate connection attempts. Handshake acknowledgments reflect the protocol state at that time and do not guarantee that the network will always be reachable afterwards.
SYN and FIN each occupy one sequence number, allowing control events to be uniformly included in ordered acknowledgment and retransmission; this cannot be reversed to mean "messages that do not occupy sequence numbers cannot be retransmitted in any protocol." Pure ACKs do not occupy sequence numbers, nor is there a separate acknowledgment for each ACK.
A normal client goes through SYN-SENT, the server goes through SYN-RECEIVED, and then enters ESTABLISHED; simultaneous open, data-carrying extensions, and abnormal resets have separate rules. SYN cookies are an implementation mechanism to resist certain half-open connection resource pressures; they have negotiation capabilities and usage conditions and cannot replace normal queue planning or all protections. RFC 9293: Connection Establishment
Cumulative ACK, SACK, and Retransmission Intervals
Assume [1001,2001) and [3001,4001) are received, with [2001,3001) lost in between. The cumulative ACK can only be 2001; SACK additionally reports [3001,4001). After filling the gap, the consecutive range extends to 4001, and the ACK can jump there.
SACK expresses reception facts; the sender still needs to maintain a scoreboard, judge loss, and schedule retransmissions. "Enabling SACK means you will always only retransmit the one piece of data that is truly lost" is too absolute; out-of-order delivery and misjudgments affect recovery. The receiver also has boundaries for withdrawing previously SACKed data, so the sender cannot discard all retransmission copies just because of SACK before the cumulative acknowledgment. RFC 2018: SACK
Loss detection and congestion control are related but distinct problems: the former determines which data might be lost, and the latter determines how much load is allowed next. Classic fast retransmission uses signals such as duplicate ACKs; modern RACK infers loss based on send time and subsequent acknowledgments; TLP uses tail probes to improve recovery for certain tail losses, without waiting for three duplicate ACKs. RFC 8985: RACK-TLP
Why RTO cannot directly equal RTT
RTO needs to leave room for delay variation. The basic estimate in RFC 6298 is SRTT + max(G, 4×RTTVAR), combined with minimums and backoff rules. When the first sample R=100 ms, SRTT=100 ms, RTTVAR=50 ms, the original calculated value is 300 ms; according to the 1 s lower bound suggested by this RFC, it becomes 1 s, and the actual kernel's lower bound implementation should be verified separately.
When the next sample is 120 ms, update RTTVAR using the old SRTT first: 0.75×50+0.25×20=42.5 ms; then update SRTT: 0.875×100+0.125×120=102.5 ms. The original RTO=272.5 ms. The ACK after retransmission may not be able to determine which send it is acknowledging; the Karn rule and timestamps are used to handle the corresponding sampling ambiguity; timeouts also trigger backoff. RFC 6298: Retransmission Timers
Where did ACK reach, and what did the app read?
Send the third range, then the first: cumulative acknowledgment advances only over contiguous data. Filling the second can advance ACK by two ranges. Read afterward to distinguish acknowledgment from consumption. Adjacent displayed SACK ranges may be merged.
Preparing the visual
Where did ACK reach, and what did the app read?
Cumulative ACK names the next expected byte. Buffered out-of-order data cannot skip a hole in delivery. Application reads do not redefine ACK.
{"id":"tcp-receive-holes","title":"Where did ACK reach, and what did the app read?","summary":"Cumulative ACK names the next expected byte. Buffered out-of-order data cannot skip a hole in delivery. Application reads do not redefine 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":"Cumulative ACK","app":"Next application read","buffer":"Buffer: blue contiguous, gold out of order","heading":"Where did ACK reach, and what did the app read?","intro":"Deliver ranges in any order, then read the contiguous prefix. Gold marks buffered data beyond a hole.","one":"Deliver [1001,2001)","read":"Application read","reset":"Reset","scope":"Three1000-byte ranges starting at sequence1001. No wrap, receive capacity or FIN. Read consumes the available prefix, not a business commit.","scopeLabel":"Model scope and assumptions","three":"Deliver [3001,4001)","two":"Deliver [2001,3001)"}}
Flow Control and Congestion Window: Deduct in-flight data first
In a normal, non-recovery simplified model, the sender is constrained by both the receive window rwnd and the congestion window cwnd. min(rwnd,cwnd) is the in-flight size constraint, not that you can send that much every time; you also need to deduct unacknowledged data, and consider pacing, available application data, and actual implementation.
Assume SND.UNA=1001, SND.NXT=4001, then there are already 3000 B unacknowledged. When rwnd=8000 B, cwnd=5000 B, the simplified new data margin is min(8000,5000)−3000=2000 B. After increasing cwnd to 20000 B, the constraint becomes the receive window, and the new margin is 5000 B.
After a zero window, the sender's persist mechanism probes whether the window has reopened, preventing long stalls caused by lost window updates; it serves a different purpose than idle connection keepalive and packet loss retransmission timers. Nagle limits small segment transmission when there is unacknowledged data, which may increase wait times for small interactions when combined with delayed ACKs; TCP_NODELAY does not restore application message boundaries.
Bandwidth-Delay Product Recalculation
For 1 Gbit/s, RTT=50 ms, the BDP is 10⁹/8×0.05=6,250,000 B. With an unscaled 65535 B window, even ignoring other constraints, the upper bound given by window/RTT is only about 10.486 Mbit/s. With scale=7, the maximum representation is 8,388,480 B, approximately 8 MiB, which can just cover this BDP; this is only a necessary budget, not a throughput guarantee.
A window is not remaining send credit
The sliders control distinct state variables. Put flight size between the windows to see why the larger limit cannot compensate for the smaller one. Shrink a limit below flight size: new sending pauses while outstanding bytes remain.
credit=max(0,min(rwnd,cwnd)−FlightSize)
Preparing the visual
A window is not remaining send credit
Take the smaller limit, then subtract flight size. A limit below flight size gives zero new credit, not negative outstanding data.
After A sends FIN, it no longer sends new application data, but can still receive data from P. After P acknowledges, it may stay in CLOSE-WAIT until the application finishes its own sending direction; then P sends FIN, and A acknowledges. ACK and FIN can be combined, so teardown does not necessarily require exactly four packets. Simultaneous teardown also changes the state path.
A typical active close side goes through FIN-WAIT-1, FIN-WAIT-2, TIME-WAIT; the passive side goes through CLOSE-WAIT, LAST-ACK. A large number of CLOSE-WAIT states should check why the application has not yet closed the connection, rather than explaining it with TIME_WAIT parameters.
TIME-WAIT reserves time to respond to retransmitted FINs and prevent old connection packets from interfering with new connections; it does not guarantee that the final ACK is definitely delivered. The specification uses 2MSL to describe it; the TCP_TIMEWAIT_LEN in Linux 7.2 is 60×HZ (60 s); distinguish the specification’s model from an implementation timer. tcp_fin_timeout applies to corresponding orphan FIN-WAIT-2 states, not as a TIME-WAIT duration switch.
Connection multiplexing can reduce connection establishment/teardown frequency; SO_REUSEADDR affects bindings that meet the conditions and cannot eliminate all four-tuple conflicts. tcp_tw_reuse also has security conditions and version configuration implications; distinguish between state and resource bottlenecks first, then discuss parameters. RFC 9293: Teardown, Linux TCP sysctl
CUBIC: How the retained fraction shapes growth
The network can reflect congestion with signals such as packet loss and ECN; it is not true that congestion control is only "guessing" and the network never actively feeds back. A core target curve for CUBIC is:
W(t) = C×(t−K)³ + Wmax
When β represents the retention ratio after congestion, K = ∛(Wmax×(1−β)/C). Taking β=0.7, C=0.4, Wmax=100 segments, K≈4.22 s; W(0)=70 segments, W(K)=100 segments. The old formula directly put the retention ratio 0.7 into the numerator of K, causing W(0) to incorrectly become 30 segments. RFC 9438: CUBIC
The curve slows down as it approaches the old Wmax and continues to probe after passing it; time is the independent variable of this function, but actual TCP still relies on ACK, RTT, Reno-friendly regions, and recovery rules, and cannot be summarized as throughput being completely unaffected by RTT. The approximate exponential growth at startup is also affected by ACK behavior and limiting conditions, and it is not guaranteed that every connection doubles exactly every RTT.
CUBIC's congestion response does not mean that every individual packet loss independently multiplies by 0.7; event merging, fast recovery, and timeouts have different handling. Queue size, AQM, ECN, random packet loss, and competing flows all affect final latency and throughput.
Why does growth slow then accelerate around the inflection?
Move through K: the window keeps growing while the slope reaches zero then rises. Changing β moves both the initial window and K, while W(K) stays at old Wmax. This exposes the purpose of the horizontal shift.
Wcubic(t)=C(t−K)3+Wmax,K=3Wmax(1−β)/C
Preparing the visual
Why does growth slow then accelerate around the inflection?
K places the flat region near old Wmax. β is the retained fraction, not the removed fraction. The cubic target is not actual sending rate.
{"id":"cubic-window-shape","title":"Why does growth slow then accelerate around the inflection?","summary":"K places the flat region near old Wmax. β is the retained fraction, not the removed fraction. The cubic target is not actual sending rate.","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":"Retained fraction β","heading":"Why does growth slow then accelerate around the inflection?","intro":"Scrub time and retention factor; inspect the curve and its slope.","reset":"Reset","scope":"Wmax100 segments, C0.4; Wcubic and derivative only. No Reno-friendly region, ACK updates, application limits or further loss.","scopeLabel":"Model scope and assumptions","slope":"Slope here (segments/s)","time":"Time since loss (s)","window":"Target window (segments)"}}
BBR: Bandwidth and delay models also need verification
BBR uses observed delivery rate and RTT to estimate path capacity, controlling transmission in combination with pacing and in-flight constraints. The typical process includes rapidly probing capacity, draining queues formed during startup, periodically probing bandwidth, and updating the minimum delay estimate. Observations are affected by ACK aggregation, application constraints, path changes, and other flows; the model does not directly read the true physical bottleneck.
The tcp_bbr.c in Linux 7.2 includes the classic four modes, a startup pacing gain of approximately 2.885, and steady-state gains such as 1.25/0.75; this cannot be generalized as a unified constant for all BBR versions. Bandwidth estimation, cwnd gain, and pacing gain are also different, and the in-flight data cannot always be precisely pinned to one BDP.
Google's BBR project includes continuously evolving implementations and materials; the compared version should be clearly specified. RFC 9438 defines CUBIC; consult the BBR project for the algorithm and version being compared. Do not replace experimental conditions with statements like "BBR never slows down due to packet loss," "must have no queues," or unspecified deployment percentages. Google BBR Project
This is a local mathematical model and does not simulate real retransmission timers, kernel congestion states, or network fairness; congestion control and connection parameters are not modified.