[{"data":1,"prerenderedAt":1067},["ShallowReactive",2],{"page-\u002Fnetwork\u002Ftcp协议":3},{"id":4,"title":5,"body":6,"description":12,"extension":1057,"meta":1058,"navigation":786,"path":1063,"seo":1064,"stem":1065,"__hash__":1066},"content\u002Fnetwork\u002FTCP协议.md","TCP协议",{"type":7,"value":8,"toc":1050},"minimark",[9,13,18,29,99,102,105,115,118,124,131,137,140,289,296,299,302,305,311,314,317,323,329,332,335,346,349,355,358,438,456,538,545,575,579,590,653,669,672,734,737,810,828,851,858,930,949,955,958,1036,1043,1046],[10,11,12],"p",{},"tcp工作在不可靠的IP层之上，它之所以可靠是因为依靠 序列号、ACK、超时重传、快速重传、校验和、连接管理。",[14,15,17],"h2",{"id":16},"tcp的可靠","TCP的可靠",[10,19,20,21,25,26],{},"TCP 有个反直觉的地方值得先说清楚：",[22,23,24],"strong",{},"它并不保证数据不丢","。它保证的是「丢了能被发现，并且补上」。所以可靠性不是靠某个黑科技，而是",[22,27,28],{},"四组机",[30,31,36,40,44,54,63,67,70,74,77,82,87,91,93,96],"svg",{"viewBox":32,"width":33,"role":34,"xmlns":35},"0 0 680 208","100%","img","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[37,38,39],"title",{},"TCP 实现可靠传输的四组机制",[41,42,43],"desc",{},"序号与确认负责发现丢包与乱序，超时与快速重传负责补发，滑动窗口负责限速，校验和与去重负责排除损坏和重复数据。",[45,46],"rect",{"x":47,"y":47,"width":48,"height":49,"rx":50,"fill":51,"stroke":52,"strokeWidth":53},"40","290","64","12","#0C447C","#85B7EB","0.5",[55,56,62],"text",{"x":49,"y":57,"dominantBaseline":58,"fill":59,"fontSize":60,"fontWeight":61},"66","central","#B5D4F4","14","500","序号 + 确认",[55,64,66],{"x":49,"y":65,"dominantBaseline":58,"fill":52,"fontSize":50},"86","每个字节编号，按序累积确认",[45,68],{"x":69,"y":47,"width":48,"height":49,"rx":50,"fill":51,"stroke":52,"strokeWidth":53},"350",[55,71,73],{"x":72,"y":57,"dominantBaseline":58,"fill":59,"fontSize":60,"fontWeight":61},"374","超时与快速重传",[55,75,76],{"x":72,"y":65,"dominantBaseline":58,"fill":52,"fontSize":50},"没等到确认就重发，超时值动态估算",[45,78],{"x":47,"y":79,"width":48,"height":49,"rx":50,"fill":80,"stroke":81,"strokeWidth":53},"124","#085041","#5DCAA5",[55,83,86],{"x":49,"y":84,"dominantBaseline":58,"fill":85,"fontSize":60,"fontWeight":61},"150","#9FE1CB","滑动窗口",[55,88,90],{"x":49,"y":89,"dominantBaseline":58,"fill":81,"fontSize":50},"170","接收窗口 + 拥塞窗口，两头都限速",[45,92],{"x":69,"y":79,"width":48,"height":49,"rx":50,"fill":80,"stroke":81,"strokeWidth":53},[55,94,95],{"x":72,"y":84,"dominantBaseline":58,"fill":85,"fontSize":60,"fontWeight":61},"校验和与去重",[55,97,98],{"x":72,"y":89,"dominantBaseline":58,"fill":81,"fontSize":50},"16 位校验和查损坏，序号重排去重",[14,100,101],{"id":101},"传输过程",[10,103,104],{},"传输过程中每个字节都有自己的编号：",[106,107,111],"pre",{"className":108,"code":110,"language":55},[109],"language-text","发送方：字节 1-1000 是第 1 段，1001-2000 是第 2 段\n接收方：收到后按序号重组，乱序也能还原\n",[112,113,110],"code",{"__ignoreMap":114},"",[10,116,117],{},"这样重复的段会按照序号丢弃，乱序到达之后会按照序号重排，确认应答后告诉对方收到了哪些。接收方收到数据之后会回复一个ACK：",[106,119,122],{"className":120,"code":121,"language":55},[109],"发送方 → [SEQ=1, 1000字节] → 接收方\n发送方 ← [ACK=1001]        ← 接收方\n",[112,123,121],{"__ignoreMap":114},[10,125,126,127,130],{},"ACK=1001 意思是\"1000 及之前的字节我都收到了，下一个期望 1001。然后这样累计确认即可。发送发发完数据之后会启动定时器，接着等待ACK，如果没有就会重传SEQ=1， ",[22,128,129],{},"超时时间（RTO）动态计算","：根据 RTT（往返时间）动态调整。快速重传不需要等待超时，收到3个重复ACK就重传。",[106,132,135],{"className":133,"code":134,"language":55},[109],"发送 [1][2][3][4][5]\n接收方收到 [1][3][4][5]，缺 [2]\n接收方回 ACK=2（重复 3 次）\n发送方收到 3 个 ACK=2 → 立即重传 [2]\n",[112,136,134],{"__ignoreMap":114},[10,138,139],{},"每个TCP都有校验和，用于检测数据是否损坏。连接管理让双方完成三次握手建立连接、四次挥手关闭，能够确保双方都准备好，确保数据都传输完成。",[30,141,143,146,149,169,177,181,188,191,195,200,203,207,213,216,222,227,230,234,237,241,244,248,251,255,258,262,265,269,275,278,282,285],{"viewBox":142,"width":33,"role":34,"xmlns":35},"0 0 680 430",[37,144,145],{},"一次丢包如何被快速重传补上",[41,147,148],{},"发送方发出 SEQ 1000 并收到确认后，SEQ 2460 在途中丢失，接收方连续回送三次重复确认 2460，触发快速重传，最终累积确认到 8300。",[150,151,152],"defs",{},[153,154,161],"marker",{"id":155,"viewBox":156,"refX":157,"refY":158,"markerWidth":159,"markerHeight":159,"orient":160},"arrow","0 0 10 10","8","5","6","auto-start-reverse",[162,163],"path",{"d":164,"fill":165,"stroke":166,"strokeWidth":167,"strokeLineCap":168,"strokeLineJoin":168},"M2 1L8 5L2 9","none","context-stroke","1.5","round",[170,171],"line",{"x1":84,"y1":172,"x2":84,"y2":173,"stroke":174,"strokeWidth":53,"strokeDashArray":175},"68","410","#888780",[176,176],"4",[170,178],{"x1":179,"y1":172,"x2":179,"y2":173,"stroke":174,"strokeWidth":53,"strokeDashArray":180},"530",[176,176],[55,182,187],{"x":84,"y":183,"textAnchor":184,"dominantBaseline":58,"fill":185,"fontSize":186,"fontWeight":61},"54","middle","#D3D1C7","13","发送方",[55,189,190],{"x":179,"y":183,"textAnchor":184,"dominantBaseline":58,"fill":185,"fontSize":186,"fontWeight":61},"接收方",[170,192],{"x1":84,"y1":193,"x2":179,"y2":193,"stroke":52,"strokeWidth":167,"markerEnd":194},"96","url(#arrow)",[55,196,199],{"x":197,"y":198,"textAnchor":184,"dominantBaseline":58,"fill":59,"fontSize":50},"340","88","SEQ 1000（1460 B）",[170,201],{"x1":179,"y1":202,"x2":84,"y2":202,"stroke":81,"strokeWidth":167,"markerEnd":194},"126",[55,204,206],{"x":197,"y":205,"textAnchor":184,"dominantBaseline":58,"fill":85,"fontSize":50},"118","ACK 2460",[170,208],{"x1":84,"y1":209,"x2":210,"y2":209,"stroke":211,"strokeWidth":167,"strokeDashArray":212},"156","326","#F09595",[158,176],[162,214],{"d":215,"fill":165,"stroke":211,"strokeWidth":167,"strokeLineCap":168},"M334 150L346 162M346 150L334 162",[55,217,221],{"x":218,"y":219,"textAnchor":184,"dominantBaseline":58,"fill":220,"fontSize":50},"236","148","#F7C1C1","SEQ 2460",[55,223,226],{"x":224,"y":209,"textAnchor":225,"dominantBaseline":58,"fill":211,"fontSize":50},"358","start","丢在途中",[170,228],{"x1":84,"y1":229,"x2":179,"y2":229,"stroke":52,"strokeWidth":167,"markerEnd":194},"186",[55,231,233],{"x":197,"y":232,"textAnchor":184,"dominantBaseline":58,"fill":59,"fontSize":50},"178","SEQ 3920（乱序，接收方先缓存）",[170,235],{"x1":179,"y1":236,"x2":84,"y2":236,"stroke":81,"strokeWidth":167,"markerEnd":194},"216",[55,238,240],{"x":197,"y":239,"textAnchor":184,"dominantBaseline":58,"fill":85,"fontSize":50},"208","ACK 2460 · 重复确认 #1",[170,242],{"x1":84,"y1":243,"x2":179,"y2":243,"stroke":52,"strokeWidth":167,"markerEnd":194},"246",[55,245,247],{"x":197,"y":246,"textAnchor":184,"dominantBaseline":58,"fill":59,"fontSize":50},"238","SEQ 5380",[170,249],{"x1":179,"y1":250,"x2":84,"y2":250,"stroke":81,"strokeWidth":167,"markerEnd":194},"276",[55,252,254],{"x":197,"y":253,"textAnchor":184,"dominantBaseline":58,"fill":85,"fontSize":50},"268","ACK 2460 · 重复确认 #2",[170,256],{"x1":84,"y1":257,"x2":179,"y2":257,"stroke":52,"strokeWidth":167,"markerEnd":194},"306",[55,259,261],{"x":197,"y":260,"textAnchor":184,"dominantBaseline":58,"fill":59,"fontSize":50},"298","SEQ 6840",[170,263],{"x1":179,"y1":264,"x2":84,"y2":264,"stroke":81,"strokeWidth":167,"markerEnd":194},"336",[55,266,268],{"x":197,"y":267,"textAnchor":184,"dominantBaseline":58,"fill":85,"fontSize":50},"328","ACK 2460 · 重复确认 #3",[55,270,274],{"x":271,"y":264,"textAnchor":272,"dominantBaseline":58,"fill":273,"fontSize":50},"142","end","#EF9F27","×3 触发",[170,276],{"x1":84,"y1":277,"x2":179,"y2":277,"stroke":273,"strokeWidth":167,"markerEnd":194},"366",[55,279,281],{"x":197,"y":224,"textAnchor":184,"dominantBaseline":58,"fill":280,"fontSize":50},"#FAC775","立刻重传 SEQ 2460，不等超时",[170,283],{"x1":179,"y1":284,"x2":84,"y2":284,"stroke":81,"strokeWidth":167,"markerEnd":194},"396",[55,286,288],{"x":197,"y":287,"textAnchor":184,"dominantBaseline":58,"fill":85,"fontSize":50},"388","ACK 8300（缺口补齐，累积确认）",[10,290,291,292,295],{},"重传达到上限（Linux 默认约 15 次）后 TCP 会放弃并报错——「可靠」不等于「一定会送达」，只等于「一定会告诉你成没成」。而且 ",[22,293,294],{},"它不保证时延","，TCP 为了可靠可以无限排队，所以时间敏感的场景宁可丢掉过期数据（这就是 WebRTC 用 UDP 的原因）。",[10,297,298],{},"顺便讲解udp，udp是故意不做可靠性保证的，他把控制器交给了应用层，udp也有校验和，但是没有序列号、ack、重传、排序、去重、流量控制等等行为。",[14,300,301],{"id":301},"tcp窗口",[10,303,304],{},"窗口是tcp的流量控制机制，接收方可以提醒发送方自己还能接受多少。接受窗口rwnd是接收方的剩余缓冲区大小：",[106,306,309],{"className":307,"code":308,"language":55},[109],"接收方缓冲区 64KB\n已用 16KB\n剩余 48KB → 告诉发送方\"窗口 = 48KB\"\n",[112,310,308],{"__ignoreMap":114},[10,312,313],{},"然后发送方就可以根据窗口大小决定能发多少，这是为了防止接收方被大量的流量淹没，有窗口才能让双方能够按照节奏来发送。",[10,315,316],{},"窗口为0的话，发送方就会停止发送，并启动零窗口探测定时器，询问接收方窗口是否开了，当接收方处理完之后窗口才会>0，然后通知发送方继续，如果接收方窗口开了但通知丢了，发送方靠探测恢复。",[10,318,319,320],{},"并且有指标可以计算：  ",[22,321,322],{},"吞吐量 = 窗口大小 \u002F RTT",[106,324,327],{"className":325,"code":326,"language":55},[109],"窗口 64KB，RTT 100ms\n吞吐量 = 64KB \u002F 0.1s = 640KB\u002Fs ≈ 5Mbps\n\n窗口 64KB，RTT 10ms\n吞吐量 = 64KB \u002F 0.01s = 6.4MB\u002Fs ≈ 51Mbps\n",[112,328,326],{"__ignoreMap":114},[10,330,331],{},"RTT越大，窗口越重要，RTT越大的话，我们自然需要窗口越大，不然吞吐量上不去。",[10,333,334],{},"TCP头部窗口字段只有16位，也就是最大64KB，如果还不够，可以使用窗口缩放选项RFC 1323，能够把窗口扩大到8MB，现代TCP窗口都会启用窗口缩放。",[10,336,337,338,341,342,345],{},"tcp窗口分几个层次：内核自动管理、系统参数调整、应用层通过socket选项来间接影响。应用层",[22,339,340],{},"看不到也改不了"," TCP 窗口字段本身。窗口是内核根据",[22,343,344],{},"接收缓冲区剩余空间","自动计算的。",[10,347,348],{},"linux可以通过命令查看：",[106,350,353],{"className":351,"code":352,"language":55},[109],"# 看系统级参数\nsysctl net.ipv4.tcp_rmem\nsysctl net.ipv4.tcp_wmem\n\n# 输出示例：\n# net.ipv4.tcp_rmem = 4096 131072 6291456\n#                    min  default  max\n",[112,354,352],{"__ignoreMap":114},[14,356,357],{"id":357},"tcp握手和挥手",[30,359,361,364,367,373,378,381,385,388,391,396,398,402,407,410,414,419,423,428,432,436],{"viewBox":360,"width":33,"role":34,"xmlns":35},"0 0 680 235",[37,362,363],{},"TCP 三次握手的报文与时序",[41,365,366],{},"客户端发 SYN 携带初始序号 x 并进入 SYN_SENT，服务端回 SYN+ACK 携带初始序号 y 并确认 x+1，客户端再回 ACK 确认 y+1，双方进入 ESTABLISHED。",[150,368,369],{},[153,370,371],{"id":155,"viewBox":156,"refX":157,"refY":158,"markerWidth":159,"markerHeight":159,"orient":160},[162,372],{"d":164,"fill":165,"stroke":166,"strokeWidth":167,"strokeLineCap":168,"strokeLineJoin":168},[170,374],{"x1":89,"y1":375,"x2":89,"y2":376,"stroke":174,"strokeWidth":53,"strokeDashArray":377},"60","215",[176,176],[170,379],{"x1":61,"y1":375,"x2":61,"y2":376,"stroke":174,"strokeWidth":53,"strokeDashArray":380},[176,176],[55,382,384],{"x":89,"y":383,"textAnchor":184,"dominantBaseline":58,"fill":185,"fontSize":186,"fontWeight":61},"48","客户端",[55,386,387],{"x":61,"y":383,"textAnchor":184,"dominantBaseline":58,"fill":185,"fontSize":186,"fontWeight":61},"服务端",[170,389],{"x1":89,"y1":390,"x2":61,"y2":390,"stroke":52,"strokeWidth":167,"markerEnd":194},"92",[55,392,395],{"x":393,"y":394,"textAnchor":184,"dominantBaseline":58,"fill":59,"fontSize":50},"335","84","① SYN · seq = x",[170,397],{"x1":61,"y1":271,"x2":89,"y2":271,"stroke":81,"strokeWidth":167,"markerEnd":194},[55,399,401],{"x":393,"y":400,"textAnchor":184,"dominantBaseline":58,"fill":85,"fontSize":50},"134","② SYN + ACK · seq = y, ack = x + 1",[55,403,406],{"x":393,"y":404,"textAnchor":184,"dominantBaseline":58,"fill":405,"fontSize":50},"166","#B4B2A9","服务端把 ACK 和 SYN 合并成一步",[170,408],{"x1":89,"y1":409,"x2":61,"y2":409,"stroke":52,"strokeWidth":167,"markerEnd":194},"192",[55,411,413],{"x":393,"y":412,"textAnchor":184,"dominantBaseline":58,"fill":59,"fontSize":50},"184","③ ACK · ack = y + 1",[55,415,418],{"x":416,"y":417,"textAnchor":272,"dominantBaseline":58,"fill":174,"fontSize":50},"162","78","CLOSED",[55,420,422],{"x":416,"y":421,"textAnchor":272,"dominantBaseline":58,"fill":174,"fontSize":50},"122","SYN_SENT",[55,424,427],{"x":416,"y":425,"textAnchor":272,"dominantBaseline":58,"fill":426,"fontSize":50},"210","#97C459","ESTABLISHED",[55,429,431],{"x":430,"y":417,"textAnchor":225,"dominantBaseline":58,"fill":174,"fontSize":50},"508","LISTEN",[55,433,435],{"x":430,"y":434,"textAnchor":225,"dominantBaseline":58,"fill":174,"fontSize":50},"172","SYN_RCVD",[55,437,427],{"x":430,"y":425,"textAnchor":225,"dominantBaseline":58,"fill":426,"fontSize":50},[10,439,440,443,444,447,448,451,452,455],{},[22,441,442],{},"为什么必须是三次，两次不行？"," 因为服务端在收到 ① 之后",[22,445,446],{},"无法确认自己的 ISN 是否被客户端收到","。如果只有两次，服务端发完 SYN+ACK 就认为连接建立，那还有一种糟糕情况：客户端很久之前发出的一个重复 SYN（网络里迷路的旧报文）突然抵达，服务端兴冲冲建好连接、分配资源，而客户端根本不知道有这条连接——资源就这么白占了，实际成了一条「幽灵连接」。第三次的 ACK 就是让服务端确认「这次握手是当下发生的一次真实往返」。",[22,449,450],{},"​三次还能顺带解决序号同步","：只有客户端回了 ",[112,453,454],{},"ack = y + 1","，服务端才知道自己的序号 y 已被对方接受。",[30,457,459,462,465,471,475,478,481,484,487,489,492,495,499,503,506,510,512,515,518,521,525,528,532,536],{"viewBox":458,"width":33,"role":34,"xmlns":35},"0 0 680 328",[37,460,461],{},"TCP 四次挥手与 TIME_WAIT 状态",[41,463,464],{},"主动方发 FIN 进入 FIN_WAIT_1，被动方回 ACK 进入半关闭，之后被动方再发自己的 FIN，主动方回 ACK 并进入 TIME_WAIT，等待 2MSL 后才真正关闭。",[150,466,467],{},[153,468,469],{"id":155,"viewBox":156,"refX":157,"refY":158,"markerWidth":159,"markerHeight":159,"orient":160},[162,470],{"d":164,"fill":165,"stroke":166,"strokeWidth":167,"strokeLineCap":168,"strokeLineJoin":168},[170,472],{"x1":89,"y1":375,"x2":89,"y2":473,"stroke":174,"strokeWidth":53,"strokeDashArray":474},"262",[176,176],[170,476],{"x1":89,"y1":473,"x2":89,"y2":477,"stroke":273,"strokeWidth":176},"308",[170,479],{"x1":61,"y1":375,"x2":61,"y2":477,"stroke":174,"strokeWidth":53,"strokeDashArray":480},[176,176],[55,482,483],{"x":89,"y":383,"textAnchor":184,"dominantBaseline":58,"fill":185,"fontSize":186,"fontWeight":61},"主动关闭方",[55,485,486],{"x":61,"y":383,"textAnchor":184,"dominantBaseline":58,"fill":185,"fontSize":186,"fontWeight":61},"被动关闭方",[170,488],{"x1":89,"y1":193,"x2":61,"y2":193,"stroke":52,"strokeWidth":167,"markerEnd":194},[55,490,491],{"x":393,"y":198,"textAnchor":184,"dominantBaseline":58,"fill":59,"fontSize":50},"① FIN · seq = u",[170,493],{"x1":61,"y1":494,"x2":89,"y2":494,"stroke":81,"strokeWidth":167,"markerEnd":194},"136",[55,496,498],{"x":393,"y":497,"textAnchor":184,"dominantBaseline":58,"fill":85,"fontSize":50},"128","② ACK · ack = u + 1",[55,500,502],{"x":393,"y":501,"textAnchor":184,"dominantBaseline":58,"fill":405,"fontSize":50},"176","半关闭：对方还可能继续发数据，ACK 与 FIN 不能合并",[170,504],{"x1":61,"y1":505,"x2":89,"y2":505,"stroke":81,"strokeWidth":167,"markerEnd":194},"206",[55,507,509],{"x":393,"y":508,"textAnchor":184,"dominantBaseline":58,"fill":85,"fontSize":50},"198","③ FIN · seq = w",[170,511],{"x1":89,"y1":243,"x2":61,"y2":243,"stroke":52,"strokeWidth":167,"markerEnd":194},[55,513,514],{"x":393,"y":246,"textAnchor":184,"dominantBaseline":58,"fill":59,"fontSize":50},"④ ACK · ack = w + 1",[55,516,517],{"x":416,"y":198,"textAnchor":272,"dominantBaseline":58,"fill":174,"fontSize":50},"FIN_WAIT_1",[55,519,520],{"x":416,"y":232,"textAnchor":272,"dominantBaseline":58,"fill":174,"fontSize":50},"FIN_WAIT_2",[55,522,524],{"x":416,"y":523,"textAnchor":272,"dominantBaseline":58,"fill":280,"fontSize":50},"280","TIME_WAIT",[55,526,527],{"x":416,"y":260,"textAnchor":272,"dominantBaseline":58,"fill":273,"fontSize":50},"等 2MSL 后关闭",[55,529,531],{"x":430,"y":530,"textAnchor":225,"dominantBaseline":58,"fill":174,"fontSize":50},"158","CLOSE_WAIT",[55,533,535],{"x":430,"y":534,"textAnchor":225,"dominantBaseline":58,"fill":174,"fontSize":50},"232","LAST_ACK",[55,537,418],{"x":430,"y":523,"textAnchor":225,"dominantBaseline":58,"fill":426,"fontSize":50},[10,539,540,541,544],{},"挥手的核心在于：",[22,542,543],{},"FIN 不是「我要关了」，而是「我这个方向没数据要发了」","。所以它天然是一个半关闭（half-close）​操作，四次挥手就是两个方向各关一次：",[546,547,548,563],"ul",{},[549,550,551,552,555,556,558,559,562],"li",{},"被动方收到 FIN 后，",[22,553,554],{},"内核立刻回 ACK","（协议栈自动完成，不等应用），此时连接变成「我可以收、你也可以继续发」——这就是 ",[112,557,531],{},"。它应用层的代码可能还在跑、还要回数据，所以",[22,560,561],{},"不能顺手把自己的 FIN 也塞进这个 ACK","。",[549,564,565,566,569,570,562],{},"等它真的处理完、调了 ",[112,567,568],{},"close()","，才发出自己的 ③ FIN。主动方回 ④ ACK 后，",[22,571,572,573],{},"不能马上关闭，要进 ",[112,574,524],{},[14,576,578],{"id":577},"ws协议","WS协议",[10,580,581,582,585,586,589],{},"WS 借用了 HTTP\u002F1.1 的 ",[112,583,584],{},"Upgrade"," 机制，",[22,587,588],{},"把同一条已经建好的 HTTP 连接上的应用层协议换掉","。没有新建连接、没有新端口、TCP 和 TLS 的握手一个都没重来——这也是 WS 能拿到极低延迟的根本原因。",[30,591,593,596,599,605,609,612,615,619,623,626,633,637,640,644,646,650],{"viewBox":592,"width":33,"role":34,"xmlns":35},"0 0 680 300",[37,594,595],{},"HTTP 与 WebSocket 的协议分层",[41,597,598],{},"WebSocket 与 HTTP 共用同一条 TCP 连接，Upgrade 换掉的只是最上面那一层应用层协议，TLS、TCP、IP 完全不变。",[150,600,601],{},[153,602,603],{"id":155,"viewBox":156,"refX":157,"refY":158,"markerWidth":159,"markerHeight":159,"orient":160},[162,604],{"d":164,"fill":165,"stroke":166,"strokeWidth":167,"strokeLineCap":168,"strokeLineJoin":168},[45,606],{"x":607,"y":47,"width":608,"height":383,"rx":157,"fill":51,"stroke":52,"strokeWidth":53},"70","200",[55,610,611],{"x":89,"y":49,"textAnchor":184,"dominantBaseline":58,"fill":59,"fontSize":60,"fontWeight":61},"HTTP\u002F1.1 请求响应",[45,613],{"x":173,"y":47,"width":608,"height":383,"rx":157,"fill":614,"stroke":273,"strokeWidth":53},"#633806",[55,616,618],{"x":617,"y":49,"textAnchor":184,"dominantBaseline":58,"fill":280,"fontSize":60,"fontWeight":61},"510","WebSocket 数据帧",[170,620],{"x1":621,"y1":49,"x2":284,"y2":49,"stroke":405,"strokeWidth":622,"markerEnd":194},"284","1",[55,624,584],{"x":197,"y":625,"textAnchor":184,"dominantBaseline":58,"fill":405,"fontSize":50},"46",[45,627],{"x":607,"y":628,"width":629,"height":630,"rx":157,"fill":165,"stroke":631,"strokeWidth":53,"strokeDashArray":632},"104","540","34","#5F5E5A",[158,176],[55,634,636],{"x":197,"y":635,"textAnchor":184,"dominantBaseline":58,"fill":174,"fontSize":50},"121","TLS —— 只有 wss:\u002F\u002F 才有，ws:\u002F\u002F 直接省略这一层",[45,638],{"x":607,"y":639,"width":629,"height":47,"rx":157,"fill":80,"stroke":81,"strokeWidth":53},"154",[55,641,643],{"x":197,"y":642,"textAnchor":184,"dominantBaseline":58,"fill":85,"fontSize":60,"fontWeight":61},"174","TCP —— 自始至终是它：可靠、有序的字节流",[45,645],{"x":607,"y":425,"width":629,"height":47,"rx":157,"fill":80,"stroke":81,"strokeWidth":53},[55,647,649],{"x":197,"y":648,"textAnchor":184,"dominantBaseline":58,"fill":85,"fontSize":60,"fontWeight":61},"230","IP",[55,651,652],{"x":607,"y":250,"textAnchor":225,"dominantBaseline":58,"fill":405,"fontSize":50},"握手阶段是普通 HTTP 报文；收到 101 之后，同一个 socket 上跑的就全是帧了",[10,654,655,656,661,662,665,666,562],{},"补一个容易踩的细节：",[22,657,658,660],{},[112,659,584],{}," 这个头只在 HTTP\u002F1.1 里存在","。所以如果要走 HTTP\u002F2（比如通过 h2 的 CDN 回源），就不能用这套升级舞步，得改用 RFC 8441 的「扩展 CONNECT」——用 ",[112,663,664],{},":protocol"," 伪头来协商 WS，机制完全不同。这也侧面说明：WS 的「升级」是",[22,667,668],{},"绑在 HTTP\u002F1.1 语义上的一个历史设计",[10,670,671],{},"接下来是握手本身。使用的是 SHA-1 + magic GUID ， RFC 6455 里的原例算了一遍对账：",[30,673,675,678,681,687,691,694,698,702,707,709,712,715,718,721,724,726,730],{"viewBox":674,"width":33,"role":34,"xmlns":35},"0 0 680 400",[37,676,677],{},"WebSocket 握手的三个步骤",[41,679,680],{},"客户端发出带 Upgrade 和随机 Key 的 HTTP 请求，服务端把 Key 拼上固定 GUID 做 SHA-1 再 base64，然后回 101 Switching Protocols 并带上 Sec-WebSocket-Accept。",[150,682,683],{},[153,684,685],{"id":155,"viewBox":156,"refX":157,"refY":158,"markerWidth":159,"markerHeight":159,"orient":160},[162,686],{"d":164,"fill":165,"stroke":166,"strokeWidth":167,"strokeLineCap":168,"strokeLineJoin":168},[45,688],{"x":47,"y":47,"width":689,"height":690,"rx":50,"fill":51,"stroke":52,"strokeWidth":53},"600","72",[55,692,693],{"x":49,"y":49,"dominantBaseline":58,"fill":59,"fontSize":60,"fontWeight":61},"① 客户端发一个普通 HTTP\u002F1.1 请求，夹带升级意图",[55,695,697],{"x":49,"y":198,"dominantBaseline":58,"fill":52,"fontSize":50,"fontFamily":696},"ui-monospace, monospace","Upgrade: websocket   ·   Sec-WebSocket-Key: 16 字节随机数的 base64",[170,699],{"x1":197,"y1":700,"x2":197,"y2":701,"stroke":405,"strokeWidth":622,"markerEnd":194},"112","168",[55,703,706],{"x":704,"y":705,"dominantBaseline":58,"fill":405,"fontSize":50},"352","140","拼接 + SHA-1 + base64",[45,708],{"x":47,"y":701,"width":689,"height":394,"rx":50,"fill":614,"stroke":273,"strokeWidth":53},[55,710,711],{"x":49,"y":409,"dominantBaseline":58,"fill":280,"fontSize":60,"fontWeight":61},"② 服务端做一次摘要，这一步没有任何秘密",[55,713,714],{"x":49,"y":236,"dominantBaseline":58,"fill":273,"fontSize":50,"fontFamily":696},"SHA1( Sec-WebSocket-Key + \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\" )",[55,716,717],{"x":49,"y":246,"dominantBaseline":58,"fill":273,"fontSize":50,"fontFamily":696},"→ s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",[170,719],{"x1":197,"y1":720,"x2":197,"y2":477,"stroke":405,"strokeWidth":622,"markerEnd":194},"252",[55,722,723],{"x":704,"y":523,"dominantBaseline":58,"fill":405,"fontSize":50},"101 之后协议切换生效",[45,725],{"x":47,"y":477,"width":689,"height":690,"rx":50,"fill":80,"stroke":81,"strokeWidth":53},[55,727,729],{"x":49,"y":728,"dominantBaseline":58,"fill":85,"fontSize":60,"fontWeight":61},"332","③ 服务端回 101 Switching Protocols",[55,731,733],{"x":49,"y":732,"dominantBaseline":58,"fill":81,"fontSize":50,"fontFamily":696},"356","Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",[10,735,736],{},"实际的报文长这样（Key 用的是 RFC 6455 里的示例值，Accept 是我上面实测算出来的）：",[106,738,742],{"className":739,"code":740,"language":741,"meta":114,"style":114},"language-http shiki shiki-themes github-light github-dark","GET \u002Fchat HTTP\u002F1.1\nHost: example.com\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\nSec-WebSocket-Version: 13\n\nHTTP\u002F1.1 101 Switching Protocols\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\n","http",[112,743,744,751,757,763,769,775,781,788,794,799,804],{"__ignoreMap":114},[745,746,748],"span",{"class":170,"line":747},1,[745,749,750],{},"GET \u002Fchat HTTP\u002F1.1\n",[745,752,754],{"class":170,"line":753},2,[745,755,756],{},"Host: example.com\n",[745,758,760],{"class":170,"line":759},3,[745,761,762],{},"Upgrade: websocket\n",[745,764,766],{"class":170,"line":765},4,[745,767,768],{},"Connection: Upgrade\n",[745,770,772],{"class":170,"line":771},5,[745,773,774],{},"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\n",[745,776,778],{"class":170,"line":777},6,[745,779,780],{},"Sec-WebSocket-Version: 13\n",[745,782,784],{"class":170,"line":783},7,[745,785,787],{"emptyLinePlaceholder":786},true,"\n",[745,789,791],{"class":170,"line":790},8,[745,792,793],{},"HTTP\u002F1.1 101 Switching Protocols\n",[745,795,797],{"class":170,"line":796},9,[745,798,762],{},[745,800,802],{"class":170,"line":801},10,[745,803,768],{},[745,805,807],{"class":170,"line":806},11,[745,808,809],{},"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\n",[10,811,812,815,816,819,820,823,824,827],{},[22,813,814],{},"那个 GUID 是写死的常量","，",[112,817,818],{},"258EAFA5-E914-47DA-95CA-C5AB0DC85B11","，全世界所有 WS 连接都一样，出现在 RFC 6455 §1.3。所以整个变换过程",[22,821,822],{},"没有任何保密性","——任何人都能复算。 ",[22,825,826],{},"客户端必须自己复算一遍并比对"," Accept，不符就直接断开，不进入帧阶段。",[10,829,830,831,834,835,838,839,842,843,846,847,850],{},"握手还有个实用细节：",[112,832,833],{},"Sec-WebSocket-Protocol"," 用来协商子协议（比如 ",[112,836,837],{},"graphql-ws","、",[112,840,841],{},"mqtt","），",[112,844,845],{},"Sec-WebSocket-Extensions"," 用来协商扩展（",[112,848,849],{},"permessage-deflate"," 就是在这里开启的压缩）。服务端选的子协议如果不合法，客户端应当断开——协议没规定要严格，但规范建议如此。",[10,852,853,854,857],{},"升完级之后，字节流上跑的就是",[22,855,856],{},"帧","了：",[30,859,861,864,867,869,874,879,882,885,889,892,895,899,903,906,909,913,917,920,923,926],{"viewBox":860,"width":33,"role":34,"xmlns":35},"0 0 680 212",[37,862,863],{},"WebSocket 帧的字节结构",[41,865,866],{},"一个 WebSocket 帧由两字节头部、可选的扩展长度、可选的四字节掩码密钥和载荷组成，掩码在客户端到服务端方向是强制的。",[45,868],{"x":47,"y":383,"width":89,"height":198,"rx":157,"fill":51,"stroke":52,"strokeWidth":53},[55,870,873],{"x":871,"y":872,"textAnchor":184,"dominantBaseline":58,"fill":59,"fontSize":186,"fontWeight":61},"125","74","头部 2 字节",[55,875,878],{"x":871,"y":876,"textAnchor":184,"dominantBaseline":58,"fill":52,"fontSize":877},"98","11","FIN · RSV · opcode",[55,880,881],{"x":871,"y":205,"textAnchor":184,"dominantBaseline":58,"fill":52,"fontSize":877},"MASK · 长度 7 位",[45,883],{"x":884,"y":383,"width":84,"height":198,"rx":157,"fill":51,"stroke":52,"strokeWidth":53},"222",[55,886,888],{"x":887,"y":872,"textAnchor":184,"dominantBaseline":58,"fill":59,"fontSize":186,"fontWeight":61},"297","扩展长度",[55,890,891],{"x":887,"y":876,"textAnchor":184,"dominantBaseline":58,"fill":52,"fontSize":877},"0 \u002F 16 \u002F 64 位",[55,893,894],{"x":887,"y":205,"textAnchor":184,"dominantBaseline":58,"fill":52,"fontSize":877},"按需出现",[45,896],{"x":897,"y":383,"width":898,"height":198,"rx":157,"fill":614,"stroke":273,"strokeWidth":53},"384","130",[55,900,902],{"x":901,"y":872,"textAnchor":184,"dominantBaseline":58,"fill":280,"fontSize":186,"fontWeight":61},"449","掩码密钥",[55,904,905],{"x":901,"y":876,"textAnchor":184,"dominantBaseline":58,"fill":273,"fontSize":877},"32 位随机数",[55,907,908],{"x":901,"y":205,"textAnchor":184,"dominantBaseline":58,"fill":273,"fontSize":877},"客户端必带",[45,910],{"x":911,"y":383,"width":912,"height":198,"rx":157,"fill":51,"stroke":52,"strokeWidth":53},"526","114",[55,914,916],{"x":915,"y":872,"textAnchor":184,"dominantBaseline":58,"fill":59,"fontSize":186,"fontWeight":61},"583","载荷",[55,918,919],{"x":915,"y":876,"textAnchor":184,"dominantBaseline":58,"fill":52,"fontSize":877},"应用数据",[55,921,922],{"x":915,"y":205,"textAnchor":184,"dominantBaseline":58,"fill":52,"fontSize":877},"长度由头部给",[55,924,925],{"x":47,"y":404,"dominantBaseline":58,"fill":174,"fontSize":50},"长度 ≤125 直接写；126 → 后接 16 位；127 → 后接 64 位",[55,927,929],{"x":47,"y":928,"dominantBaseline":58,"fill":273,"fontSize":50},"190","客户端 → 服务端必须掩码；服务端 → 客户端必须不掩码",[10,931,932,933,936,937,940,941,944,945,948],{},"TCP 只提供「有序的字节流」，",[22,934,935],{},"它没有消息边界","——你发了两个消息，对端可能一次 ",[112,938,939],{},"read"," 就收到粘在一起的两个，也可能被拆成三块。WS 的帧就是来补这个缺口的：",[112,942,943],{},"FIN"," 标记消息结束，",[112,946,947],{},"opcode"," 标记这条消息是什么类型，于是「消息」这个概念第一次在协议层被定义出来。这是 WS 相对裸 TCP 最核心的增值（其次是它能穿过 80\u002F443 的代理和防火墙）。",[10,950,951,952],{},"它的报文形式：",[22,953,954],{},"​WS 的消息是二进制帧",[10,956,957],{},"ws客户端的文本帧字节可能长这样：",[30,959,961,964,967,973,978,985,990,992,996,999,1002,1006,1010,1013,1017,1021,1024,1028,1032],{"viewBox":960,"width":33,"role":34,"xmlns":35},"0 0 680 326",[37,962,963],{},"WebSocket 客户端文本帧的字节结构",[41,965,966],{},"一条 26 字节的客户端文本帧由 2 字节帧头、4 字节掩码键和 20 字节掩码后的载荷组成，载荷与掩码键逐字节异或可还原原文。",[55,968,972],{"x":47,"y":969,"fontSize":60,"fontWeight":61,"fill":970,"fontFamily":971},"32","var(--color-text-primary)","ui-sans-serif, system-ui, sans-serif","客户端 → 服务端：一条文本帧，共 26 字节",[45,974],{"x":375,"y":975,"width":976,"height":977,"rx":159,"fill":51,"stroke":52,"strokeWidth":53},"58","44","30",[55,979,984],{"x":980,"y":981,"fontSize":50,"textAnchor":184,"dominantBaseline":58,"fill":982,"fontFamily":983},"82","73","#E6F1FB","ui-monospace, Consolas, monospace","81",[55,986,989],{"x":987,"y":981,"fontSize":50,"dominantBaseline":58,"fill":988,"fontFamily":971},"116","var(--color-text-secondary)","FIN=1 + opcode=0x1 → text 帧",[45,991],{"x":375,"y":193,"width":976,"height":977,"rx":159,"fill":51,"stroke":52,"strokeWidth":53},[55,993,995],{"x":980,"y":994,"fontSize":50,"textAnchor":184,"dominantBaseline":58,"fill":982,"fontFamily":983},"111","94",[55,997,998],{"x":987,"y":994,"fontSize":50,"dominantBaseline":58,"fill":988,"fontFamily":971},"MASK=1 + payload 长度 20",[45,1000],{"x":375,"y":400,"width":1001,"height":977,"rx":159,"fill":614,"stroke":273,"strokeWidth":53},"110",[55,1003,1005],{"x":872,"y":1004,"fontSize":50,"dominantBaseline":58,"fill":280,"fontFamily":983},"149","37 FA 21 3D",[55,1007,1009],{"x":1008,"y":1004,"fontSize":50,"dominantBaseline":58,"fill":988,"fontFamily":971},"182","masking-key：4 字节，明文写在帧里",[45,1011],{"x":375,"y":434,"width":236,"height":977,"rx":159,"fill":1012,"stroke":405,"strokeWidth":53},"#444441",[55,1014,1016],{"x":872,"y":1015,"fontSize":50,"dominantBaseline":58,"fill":185,"fontFamily":983},"187","4C D8 55 44 47 9F 03 07 …",[55,1018,1020],{"x":1019,"y":1015,"fontSize":50,"dominantBaseline":58,"fill":988,"fontFamily":971},"288","掩码后的载荷（20 字节）",[55,1022,1023],{"x":375,"y":534,"fontSize":186,"dominantBaseline":58,"fill":988,"fontFamily":971},"把载荷逐字节 XOR 掩码键，还原出原文：",[45,1025],{"x":375,"y":1026,"width":197,"height":47,"rx":157,"fill":1027,"stroke":426,"strokeWidth":53},"248","#27500A",[55,1029,1031],{"x":417,"y":253,"fontSize":186,"dominantBaseline":58,"fill":1030,"fontFamily":983},"#C0DD97","{\"type\":\"heartbeat\"}",[55,1033,1035],{"x":47,"y":257,"fontSize":877,"dominantBaseline":58,"fill":1034,"fontFamily":983},"var(--color-text-tertiary)","完整字节：81 94 37 FA 21 3D 4C D8 55 44 47 9F 03 07 15 92 44 5C 45 8E 43 58 56 8E 03 40",[10,1037,1038,1039,1042],{},"这个和 SSE 的差别一眼就能看出来：",[22,1040,1041],{},"SSE 是一行行文本，WS 是一串字节","。帧头就两个字节，拆开看全是位域。",[10,1044,1045],{},"继续更新。。。",[1047,1048,1049],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":114,"searchDepth":753,"depth":753,"links":1051},[1052,1053,1054,1055,1056],{"id":16,"depth":753,"text":17},{"id":101,"depth":753,"text":101},{"id":301,"depth":753,"text":301},{"id":357,"depth":753,"text":357},{"id":577,"depth":753,"text":578},"md",{"date":1059,"tags":1060},"2026-12-10",[1061,1062],"network","tcp","\u002Fnetwork\u002Ftcp协议",{"description":12},"network\u002FTCP协议","_XJVkL5T4NvisWjWUM6Y5mG7R7r2gUZ4wElxZOpUHgU",1791808060785]