
三次握手全程只发 3 个包SYN、SYNACK、ACK客户端侧耗时 1 个 RTT。丢第一个 SYN客户端按 RTO 翻倍重传1 次之后建连成功。服务端完全不回应时客户端重传 3 次、RTO 涨到 8 个 RTT 才放弃。这些数字不是背下来的是我在页面上一帧一帧数出来的。在线案例与源码https://atomgit.com/deli007/demo_park/tree/main/codearts-tcp-handshake-lab成品只有一个 index.html双击就能用不装任何依赖。一、为什么值得做这题人人会背但很少有人把丢包跑一遍搜「TCP 三次握手」出来的答案几乎一样画三个箭头写一句「确认双方收发能力」就结束了。看完你还是不知道丢一个包会发生什么重传几次算完服务端一直不回又是什么下场。我把五条链路做成一条时间轴每一步都停在那一帧上SEQ 和 ACK、标志位、两端状态机、当前 RTO 全写在旁边。二、准备环境打开码道 Web把需求写成一段话浏览器打开码道 Web 版https://devcloud.cn-north-4.huaweicloud.com/chat?sourcedmzntgwsfsourceaddmzntgwwbwz 登录后在对话框里写需求就能拿到一个能跑的单文件应用。码道有三种使用方式WebUI浏览器对话、TUI终端命令行和桌面 IDEIDE 插件。本文用 WebUI 版演示。需求我写了 7 条事后看最值钱的是第 2 条和第 6 条。第 2 条要求五种丢包场景第 6 条要求页面自己有自检脚本并把结果打出来。有了这两条页面就不是一张静态示意图而是一台能改参数、能自己验算的模拟器。三、需求原文可以照着复制做一个单文件网页应用 index.html原生 HTMLCSSJS无外链、无 CDN、无构建、可离线打开主题是 TCP 三次握手与四次挥手可视化实验室。 1. 时序图动画逐步演示 SYN→SYNACK→ACK 三次握手与 FIN→ACK→FINACK→ACK 四次挥手每步显示方向、SEQ/ACK 序号、标志位、当前状态机CLOSED/LISTEN/SYN_SENT/ESTABLISHED/FIN_WAIT_1/CLOSE_WAIT 等与已消耗的 RTT 数。 2. 场景开关正常、丢第一个 SYN、丢 SYNACK、丢最后的 ACK服务端已建连、客户端仍认为未建连的半开连接、服务端不响应超时重传若干次后放弃丢包要按 RTO 翻倍重传并显示当前 RTO。 3. 控制条下一步、自动播放、暂停、重置、速度 0.5x~4x。 4. 统计面板报文总数、消耗 RTT 数、总耗时、重传次数。 5. 点任意报文可展开头部字段SEQ、ACK、SYN、FIN、窗口。 6. 页面上必须有自检脚本并把每条断言结果显示出来例如正常握手报文数3、正常握手耗时1 RTT、四次挥手耗时2 RTT、丢包场景重传次数k改动参数后重跑自检。 7. 输入非法参数比如重传次数填负数或 0、窗口填非数字要给明确中文提示并保留上一次有效值。 完成后打开预览让我直接看到运行效果。四、页面长什么样一条时间轴五个场景上半部分是场景开关、链路参数和播放控制右上角是统计面板中间那条时间轴就是全部剧情。下图是正常链路点到最后一步客户端和服务端都进了 ESTABLISHED握手报文停在 3 个。点任意一条报文会弹出它的头部字段方向、SEQ、ACK、SYN 与 FIN 标志位、窗口大小、链路是否送达。五、实测三次握手就是 3 个包客户端 1 个 RTT正常链路下握手阶段恰好 3 个报文客户端在收到 SYNACK 的那一刻就认为自己建连成功此时耗时 1.0 RTT。服务端要晚半拍它得等第三个 ACK 飞完那 0.5 RTT 才进 ESTABLISHED。所以截图里那个「消耗 RTT 1.5」不是笔误是两端对「什么时候算连上了」的理解本来就差了半个往返。挥手阶段是 4 个报文、2 个 RTT整个正常场景从头跑到尾是 4.5 个 RTT。六、丢一个包多的不只是一个包五种场景我都跑到最后一帧数字列在下面。场景报文总数重传次数客户端 RTO 变化握手耗时结果正常链路701 个 RTT不变1.0 RTT成功丢第一个 SYN811 → 22.0 RTT成功丢 SYNACK911 → 22.0 RTT成功丢最后 ACK91服务端发起服务端 1 → 22.5 RTT成功服务端不响应431 → 2 → 4 → 8放弃失败丢 SYNACK 那一场我停在客户端收到重传后的 SYNACK 这一帧时间轴上那个红色叉号就是被丢掉的包。服务端一直不回是最极端的一种客户端把 RTO 从 1 一路翻倍到 8重传 3 次后放弃。那一帧里客户端一直停在 SYN_SENT服务端一直停在 LISTEN两端谁也没往前一步。七、核心那段代码RTO 翻倍和状态迁移写在一起不响应场景的重传循环是整页里最像协议的一段去掉旁白之后是这样。functiontimeoutEvt(side,rtoOld,rtoNew,from,to){varwhosideC?C:S;who.rtortoNew;}varddlR0;vari0;while(iMR){varcurFromt;if(ddlt){wait(ddl-t,等待 RTO 超时);}varrtoOldC.rto,rtoNewMath.round(rtoOld*2*100)/100;timeoutEvt(C,rtoOld,rtoNew,curFrom,t);i;retransCount();varsendAtt;fly({from:C,to:S,flags:[SYN],seq:ISN_C,ack:null,win:W,retrans:i,rto:rtoNew});ddlsendAtC.rto;}每次超时都先把 RTO 乘 2 写回本地时钟再发一个重传的 SYN序号保持不变。循环的出口是 i 撞上 MR也就是界面上的「最大重传次数」所以重传上限是参数化的。八、验证与踩坑页面加载后自己跑一遍 18 条断言内置参数下 18/18 通过。前 14 条盯住五个场景的报文数、耗时、重传次数和 RTO 倍数后 4 条专门验参数校验函数。断言写的是关系式而不是常量。比如「丢第一个 SYN握手耗时 前 RTO 1 RTT」我把初始 RTO 从 1 改成 0.5这条仍然成立实测 1.5 RTT。我又用页面自带的 simulate() 跑了几组参数做交叉验证。窗口大小填 1024 和填 65535报文数、总耗时、内部步数完全一样都是 7 个报文、4.5 RTT、39 步。也就是说这个参数只写进了报文头部没有参与任何计算。把最大重传次数改成 5不响应场景变成 6 个 SYN、RTO 涨到 32、31.5 RTT 后才放弃。第二个边界是时间模型单向传输固定按 0.5 RTT 算真实链路里 SYN 和 SYNACK 的时延并不相等。第三个边界是数据的口径报文计数把丢包的也算「发出」丢 SYNACK 场景最终是 9 个报文其中途丢掉的 SYNACK 照样算一次发送。所以统计面板上的「报文总数」是发送动作数不是到达数。第四个边界是 RTO 只涨不落。真实实现按 SRTT 和 RTTVAR 估算 RTO超时后退避拿到新的 RTT 样本再收敛回来这个页面从初始值一路翻倍成功建连之后也不复原。失败路径我也点了一遍把最大重传次数填成 0。页面给的原话是「最大重传次数无效必须为 1 ~ 6 之间的整数已恢复为上一次有效值 3」输入框随即变回 3时间轴也没有被清空。九、实测数据汇总项目实测值正常链路报文数7握手 3 加挥手 4正常链路握手耗时客户端 1.0 RTT服务端进 ESTABLISHED 是 1.5 RTT正常链路全场景耗时4.5 RTT服务端不响应4 个 SYN、3 次重传、RTO 到 8、7.5 RTT 后放弃页面自检18/18 通过页面网络请求0 条单个 HTML 体积43875 个字符无外链「网络请求 0 条」是用浏览器的性能接口复核的打开页面到把五个场景点完资源列表一直是空的。十、本地复现双击 index.html 就能用不需要装任何依赖。想走静态服务python -m http.server 8000 然后访问 http://localhost:8000 。想看它有没有偷偷联网按 F12 打开网络面板再切几次场景列表里不会多出请求。十一、说点不足窗口大小只显示、不参与计算真实 TCP 的流控和窗口缩放都没做。只实现了超时重传快速重传和 SACK 都没有。挥手进了 TIME_WAIT 之后直接进 CLOSED没有真正等 2MSL。ESTABLISHED 之后的数据交互用一段 0.5 RTT 的空档代替没有画数据报文。直接下载试玩这个 Demo 已经完整开源源代码、本文档和全部截图都在公开仓库里可以直接下载来玩Demo Park 仓库https://atomgit.com/deli007/demo_park本案例目录https://atomgit.com/deli007/demo_park/tree/main/codearts-tcp-handshake-lab打开上面的链接进入 codearts-tcp-handshake-lab 文件夹把 index.html 下载到本地用浏览器打开即可运行。十二、用码道做这个页面的体会第一条是需求里先写清楚期望值。我直接把「正常握手报文数3」「四次挥手耗时2 RTT」写进需求它就把这些变成了页面上的断言。第二条是自检要写成关系式。关系式在改参数之后仍然成立写死的常量只能证明一份数据对。第三条是失败路径单独交代。我写了非法参数要给中文提示并保留上一次有效值交回来的就是输入框变回 3、时间轴不清空。第四条是统计面板真的有用。报文总数、重传次数、当前 RTO 这三格是整篇文章所有数字的出处。第五条是生成物要自己验。五条场景我都在浏览器里点到底窗口参数、重传上限、初始 RTO 又各换了一组跑过才敢把表格贴上来。