The boundaries of send do not become the boundaries of recv verbatim, and ACKs do not imply that the peer has committed business data. TCP provides an ordered, reliable byte stream. When understanding the Linux implementation, first separate sequences, windows, recovery, and shutdown directions.
For packet headers, options, and protocol-level window calculations, start with TCP in the networking series. This article follows Linux connection queues, receive/send paths, and socket behavior; the shared sequence and window models connect the protocol to its implementation.
Target of Reference
Reading Location
Routing and SYN Queue
Sections 1–2
Byte Sequences, BDP, and Loss
Sections 3–5
Control Algorithms and Fast Path
Sections 6–7
Half-Close Experiment
Section 8
1. IP Routing Determines Local Delivery or Forwarding
After verifying the basic header, IPv4 undergoes processing such as PREROUTING before making a routing decision. Packets destined for the local machine enter LOCAL_IN/INPUT and are handed to TCP, UDP, etc.; the forwarding path goes through FORWARD and egress processing. Local sending requires initial routing information; address/mark changes in OUTPUT may trigger re-routing, after which the packet enters POSTROUTING.
IPv6 has its own input and routing implementation: the basic header lacks an IPv4 header checksum, and extension header processing and fragmentation rules differ. IPv6 routers do not fragment on behalf of the source along the path; therefore, mechanisms like Path MTU Discovery must be handled correctly. Do not simply treat IPv4 function chains as a universal protocol stack.
2. The Three-Way Handshake Establishes Connection State
A normal active open goes through SYN_SENT, then advances to ESTABLISHED upon receiving a valid response. The server's listening socket continues to listen; half-open connections are typically tracked using a lighter-weight request socket, and only after the handshake is complete do they enter the accept queue. SYN cookies, TCP Fast Open, etc., further alter specific resources and timing.
The SYN queue and the accept queue are at different stages. listen(backlog) does not set a single total count for all half-open connections. If accept returns too slowly, it may block the queue of completed handshakes; this cannot be solved solely by increasing SYN-related parameters.
The handshake exchanges initial sequence numbers and options, and the SYN itself consumes one sequence number. A successful TCP establishment only indicates that the transport connection is available, not that the application has authenticated or that business services are ready. See RFC 9293 for protocol specifications.
3. Sequences, ACKs, and the Receive Queue
Received Range
Receive State
Cumulative ACK
[1000,2000) received in order
Next expected byte is 2000
2000
[3000,4000) arrives first
Gap remains in the middle
Still 2000
Gap filled subsequently
Continuous receive position can advance
Advances with the continuous range
TCP sequence numbers count bytes. SACK can report discrete received ranges separately, allowing the sender to choose retransmissions more accurately; cumulative ACKs do not skip gaps that have not yet been filled.
The peer acknowledging bytes entering the receive protocol state does not mean the peer application has read them, nor does it mean business writes to the database have been committed. send returning does not equal receiving an ACK. If the application requires message boundaries or completion acknowledgments, it must define length, delimiters, request IDs, and response protocols.
Linux can place in-order data into the receive queue and store out-of-order data in corresponding structures; ordered byte streams are delivered semantically when the application reads. PSH does not constitute a reliable message boundary; recv(4096) may return fewer than 4096 bytes, or it may merge data from multiple sends in one go.
Linux retains ordered and out-of-order data in corresponding structures and delivers an ordered byte stream. PSH is not a reliable message delimiter. recv(4096) can return fewer bytes or combine data from several sends. Click arbitrary segments to introduce reordering and duplicates; application consumption is counted separately.
Preparing the visual
Later bytes arrived: why is ACK still at the gap?
Cumulative ACK names the next byte not yet received contiguously; SACK describes later disjoint ranges. Protocol acknowledgement is not application consumption, and duplicate data does not advance ACK again.
{"id":"tcp-cumulative-ack","title":"Later bytes arrived: why is ACK still at the gap?","summary":"Cumulative ACK names the next byte not yet received contiguously; SACK describes later disjoint ranges. Protocol acknowledgement is not application consumption, and duplicate data does not advance ACK again.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><p class=\"caption\" data-i18n=\"arrive\"></p><div class=\"choices\"><button data-segment=\"0\">1<small>[1000,2000)</small></button><button data-segment=\"1\">2<small>[2000,3000)</small></button><button data-segment=\"2\">3<small>[3000,4000)</small></button><button data-segment=\"3\">4<small>[4000,5000)</small></button><button data-segment=\"4\">5<small>[5000,6000)</small></button><button data-segment=\"5\">6<small>[6000,7000)</small></button><button data-segment=\"6\">7<small>[7000,8000)</small></button><button data-segment=\"7\">8<small>[8000,9000)</small></button></div><div class=\"track-row\"><span data-i18n=\"stream\"></span><div id=\"stream\" class=\"track\"></div></div><div class=\"metrics\"><div><span data-i18n=\"ack\"></span><strong id=\"ack\"></strong></div><div><span data-i18n=\"consumed\"></span><strong id=\"consumed\"></strong></div></div><div class=\"formula\" id=\"sack\"></div><p class=\"caption\" data-i18n=\"last\"></p><div id=\"last\" class=\"readout\"></div><div class=\"actions\"><button id=\"read\" data-i18n=\"read\"></button></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"note\"></p></div><div class=\"actions\"><button id=\"reset\" data-i18n=\"reset\"></button></div><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.choices button{min-height:65px}.choices small{display:block;font:10px var(--viz-font-ui);overflow-wrap:anywhere;margin-top:5px}.choices button.received{background:var(--accent-soft)}#sack{min-height:6.5em;white-space:pre-line}.readout{min-height:3.2em}","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)));}let received=Array(8).fill(false),consumed=0,last='—';function contiguous(){const i=received.indexOf(false);return i<0?8:i;}function draw(){const n=contiguous(),sack=[];for(let i=n;i<8;){if(!received[i]){i++;continue;}let j=i+1;while(j<8&&received[j])j++;sack.push('['+(1000+i*1000)+','+(1000+j*1000)+')');i=j;}cells('stream',received.map((v,i)=>({text:String(i+1),cls:i<consumed?'gold':v?'on':'empty'})));document.querySelectorAll('[data-segment]').forEach(e=>e.classList.toggle('received',received[+e.dataset.segment]));set('ack',1000+n*1000);set('consumed',consumed*1000);set('sack','SACK\\n'+(sack.join(' ')||'—'));set('last',last);$('#read').disabled=consumed>=n;set('explanation',t('note'));}document.querySelectorAll('[data-segment]').forEach(e=>e.onclick=()=>{const i=+e.dataset.segment;last='['+(1000+i*1000)+','+(2000+i*1000)+')'+(received[i]?' ×2':'');received[i]=true;draw();});$('#read').onclick=()=>{if(consumed<contiguous())consumed++;draw();};$('#reset').onclick=()=>{received=Array(8).fill(false);consumed=0;last='—';draw();};draw();","audio":false,"strings":{"ack":"Cumulative ACK / byte sequence","arrive":"Click to deliver this range","consumed":"Application bytes consumed","heading":"Later bytes arrived: why is ACK still at the gap?","intro":"Click a number to deliver its 1000-byte range; click again to duplicate it. Fill gaps in any order, then let the application read.","last":"Last arriving range","note":"Click 3 and 4, then 1. With range 2 missing, ACK stays at 2000. Filling 2 advances across the later data already retained.","read":"Application reads next range","reset":"Reset","scope":"Eight 1000-byte segments, initial RCV.NXT=1000. SYN/FIN, wraparound, receive-window limits, and ACK transmission delays are omitted. SACK lists logical ranges, not option-space limits or reporting order. Each application read consumes one contiguous segment.","scopeLabel":"Model scope and assumptions","stream":"Stream positions: blue received, gold read by application"}}
4. Flow Control and Congestion Control Are Two Different Rulers
The receive window (rwnd) indicates how much data the receiver can still accept; the congestion window (cwnd) indicates the sender's limit on the amount of in-flight data the network can bear. Their units may differ in implementation and display; for example, cwnd is often counted in MSS segments, requiring conversion before comparison.
Roughly speaking, the amount of new data that can continue to be sent is limited by min(cwnd_bytes, rwnd_bytes) - bytes_in_flight, while also considering pacing, whether the application has data, retransmissions, and implementation details. The window is not a direct unit of sending rate.
Shrinking a limit does not erase already outstanding bytes. Change each limit and then try sending.
new_data≤max(0,min(cwnd,rwnd)−flight)
All quantities are bytes. This compares current sending allowance, omitting congestion-algorithm evolution, SACK pipe estimates, and the real window-update protocol.
Preparing the visual
Two window limits subtract the same in-flight bytes
New-data allowance is max(0, min(cwnd, rwnd) − flight). A smaller window can leave flight above the new limit; new sends stop until acknowledgements release capacity.
{"id":"tcp-flight-window","title":"Two window limits subtract the same in-flight bytes","summary":"New-data allowance is max(0, min(cwnd, rwnd) − flight). A smaller window can leave flight above the new limit; new sends stop until acknowledgements release capacity.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><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=\"16\" value=\"8\" step=\"1\"></div><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=\"16\" value=\"12\" step=\"1\"></div><p class=\"caption\" data-i18n=\"c\"></p><div class=\"band\"><i id=\"bar-c\"></i></div><p class=\"caption\" data-i18n=\"r\"></p><div class=\"band\"><i id=\"bar-r\"></i></div><p class=\"caption\" data-i18n=\"f\"></p><div class=\"band\"><i id=\"bar-f\" class=\"second\"></i></div><div class=\"axis\"><span>0</span><span>8000</span><span>16000 B</span></div><div class=\"actions\"><button id=\"send\" data-i18n=\"send\"></button><button id=\"ack\" data-i18n=\"ack\"></button></div><div class=\"metrics\"><div><span data-i18n=\"flight\"></span><strong id=\"flight\"></strong></div><div><span data-i18n=\"allowance\"></span><strong id=\"allowance\"></strong></div></div><div class=\"formula\" id=\"arithmetic\"></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"note\"></p></div><div class=\"actions\"><button id=\"reset\" data-i18n=\"reset\"></button></div><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#arithmetic{min-height:6em}","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)));}let flight=0;function draw(){const c=+$('#cwnd').value*1000,r=+$('#rwnd').value*1000,a=Math.max(0,Math.min(c,r)-flight);set('cwnd-value',c+' B');set('rwnd-value',r+' B');for(const[k,v]of [['c',c],['r',r],['f',flight]])$('#bar-'+k).style.width=v/160+'%';set('flight',flight);set('allowance',a);set('arithmetic','max(0, min('+c+', '+r+') − '+flight+') = '+a+' B');$('#send').disabled=a<1000;$('#ack').disabled=flight===0;set('explanation',t('note'));}$('#cwnd').oninput=$('#rwnd').oninput=draw;$('#send').onclick=()=>{if(Math.min(+$('#cwnd').value,+$('#rwnd').value)*1000-flight>=1000)flight+=1000;draw();};$('#ack').onclick=()=>{flight=Math.max(0,flight-1000);draw();};$('#reset').onclick=()=>{flight=0;draw();};draw();","audio":false,"strings":{"ack":"ACK 1000 B","allowance":"New-data allowance / B","c":"Congestion window","cwnd":"cwnd / 1000 B","f":"Sent, not acknowledged","flight":"Bytes in flight","heading":"Two window limits subtract the same in-flight bytes","intro":"Adjust cwnd and rwnd, then send or acknowledge 1000 bytes. Send several segments before shrinking a window: existing in-flight data does not disappear.","note":"Blue bars show current limits; gold shows in-flight data. Both limits constrain the same connection; cwnd is not spent first and rwnd afterward.","r":"Receive window","reset":"Reset","rwnd":"rwnd / 1000 B","scope":"All quantities are bytes, 1000 B per unit, maximum 16000 B. Congestion algorithms, pacing, window scaling, retransmission, and zero-window probes are omitted. The rwnd slider compares constraint snapshots, not advice to retract an advertised receive-window right edge.","scopeLabel":"Model scope and assumptions","send":"Send 1000 B"}}
Calculating In-Flight Data: Bandwidth May Be Sufficient, But the Window May Still Limit Throughput
Condition or Calculation
Result
Link 1 Gbit/s, RTT 20 ms
BDP is approx. 2.5 MB, i.e., 2.38 MiB
Effective in-flight data is only 64 KiB
Far smaller than the BDP for this path
64 KiB / 0.02 s
Simplified throughput upper limit of approx. 3.28 MB/s
Other limitations are ignored here. The calculation shows that insufficient window size limits throughput; it does not imply that increasing buffers solves all performance issues.
Increasing buffers can eliminate certain limitations but cannot fix packet loss, incorrect MTU, congestion, or slow applications. Receive auto-tuning has upper limits, memory pressure, and user setting constraints; setting parameters like SO_RCVBUF also affects auto-tuning behavior. One cannot promise that "high BDP never requires checking buffers." See TCP sysctl.
5. Loss Detection Is Not Just Timeout
Information such as duplicate ACKs, SACK, and time-based inference via RACK can detect loss before the RTO expires. RTO remains an important fallback mechanism for recovery, based on smoothed RTT and variance estimation, with backoff after timeout.
RFC 6298 provides the RTO calculation framework; specific Linux minimums, paths, and configurable items should be checked against the target version. Do not treat RFC recommended values as fixed values for all Linux timers. RTT samples after retransmission are ambiguous; estimation cannot directly attribute any ACK to the most recent send.
Timers Classified by Purpose: Window, Idle, and Loss
Zero-window probes handle cases where the receiver's window is closed; keepalive checks the reachability of long-idle connections. Neither is a rename of the standard loss retransmission timer. TCP retry timeouts also do not guarantee that the peer application hasn't already processed previous data; business retries require their own deduplication semantics.
6. CUBIC and BBR: Compare Control Objectives, Don't Accept Marketing Claims
CUBIC's window growth is designed around the window value before the last congestion event; the cubic function alters growth behavior as it approaches or moves away from the old peak. It also includes details such as TCP-friendly behavior. You cannot replace the entire algorithm with a single cubic formula. RFC 9438 is the specification for CUBIC, not for BBR.
BBR constructs a path model using estimated delivery rate and propagation RTT, controlling sending via pacing and in-flight limits. Different BBR versions handle packet loss, ECN, fairness, and probing differently; you cannot uniformly say it "completely ignores packet loss" or that it "is always better than CUBIC on any congested link." The actual kernel module name does not automatically prove the adoption of a specific paper/patch version.
The system default algorithm affects new connections; existing sockets can have their own settings. When comparing, fix the path, RTT, number of flows, receive window, and bottleneck queue, then observe throughput, latency, and co-existing flow performance.
7. The Fast Path Does Not Bypass Protocol Correctness
tcp_rcv_established() contains optimized processing for common established connection packets. When conditions like header prediction are met, general state processing overhead is reduced; out-of-order packets, abnormal flags, window changes, options, etc., cause processing to take other branches.
You cannot deduce from the function name that "90% of packets execute only dozens of instructions." Checksum offload, GRO, lock contention, socket backlog, memory allocation, and wake-ups all affect cost. When a socket is occupied by user context, packets may be queued in the backlog and subsequently processed by the appropriate path.
MSG_DONTWAIT only controls whether the current read/write operation waits; it cannot be treated as a condition for the kernel to directly write received packets into the user buffer. The source code should be read in conjunction with net/ipv4/tcp_input.c, tcp.c, and tcp_output.c.
8. Closing One Direction Does Not Mean Closing the Entire Connection
Situation
Typical State/Behavior
Explanation
Actively sending FIN
FIN_WAIT1, then FIN_WAIT2 after confirmation
Local sending direction is closed, but receiving is still possible
Receiving peer's FIN
CLOSE_WAIT
Peer will no longer send data; local application can still send
Passive party subsequently closes
LAST_ACK
Waiting for local FIN to be acknowledged
Active party completes bidirectional close
TIME_WAIT
Protocol requirements such as handling late packets and final ACK retransmission
Both parties send FIN crosswise
May pass through CLOSING
Not always strictly sequential along the same path
TIME_WAIT and FIN_WAIT2 are different states; do not assume tcp_fin_timeout is a universal switch for TIME_WAIT duration. Long-term accumulation of CLOSE_WAIT often requires checking application close behavior; a large number of TIME_WAIT states cannot be judged as a leak solely by quantity.
Runnable Half-Close Example
The following uses only local loopback and temporary ports. The client closes the sending direction after writing; the server can still return the count of received bytes after seeing EOF.
importsocketimportthreadingerrors=[]withsocket.socket()aslistener:listener.bind(('127.0.0.1',0))listener.listen(1)listener.settimeout(3)defserve():try:conn, _=listener.accept()withconn:conn.settimeout(3)size=0whiledata:=conn.recv(1024):size+=len(data)conn.sendall(str(size).encode('ascii'))exceptExceptionasexc:errors.append(exc)worker=threading.Thread(target=serve)worker.start()try:withsocket.create_connection(listener.getsockname(),timeout=3)asclient:client.sendall(b'hello')client.shutdown(socket.SHUT_WR)parts=[]whiledata:=client.recv(1024):parts.append(data)assertb''.join(parts)==b'5'finally:worker.join(timeout=4)assertnotworker.is_alive()iferrors:raiseerrors[0]print('half-close: reply received after SHUT_WR')
This protocol deliberately uses EOF as the request boundary; long-lived multiplexed connections generally require setting length or frame formats separately. It illustrates TCP half-close semantics, but does not simulate network disconnection, retransmission, or application idempotency.