Skip to content

第 10 章 WebSocket:HTTP 协商之后的双向帧协议 ​

返回目录 · 编写进度

WebSocket借用HTTP建立一个被双方明确接受的会话,随后用自己的帧格式传送双向消息。它不是“不断重复HTTP请求”,也不是去掉所有边界之后的裸TCP。理解它如何承载代理,需要先看清握手、消息、帧和底层流的区别。

10.1 双向通信需要一种怎样的服务 ​

网页聊天、事件推送和交互应用希望服务器在有新数据时直接发送,而不必等待客户端每次发起独立查询。轮询可以提供近似效果,但带来重复请求、等待和状态管理;长轮询延长等待,又需要处理响应结束后重新发起的连接周期。WebSocket为这类应用提供持续的双向消息服务。

这并不意味着HTTP/1.1在物理上只能单向传输。TCP本来就有两个方向,HTTP请求和响应内容也能在适用条件下流式处理;问题在于浏览器API、HTTP请求响应语义与中间设备,是否共同提供应用所需的持续消息抽象。WebSocket把这个抽象作为专门协议定义,而非让每个网页自行模拟一套轮询约定。RFC 6455 §1。

一条WebSocket会话建立以后,两边都能发送文本消息或二进制消息;消息又可以拆成多个帧,帧之外还有Ping、Pong和Close等控制。接收应用通常关注消息,而底层编码器还必须维护帧、掩码和分片状态。因此“有一个send调用”不意味着线上一个TCP段,也不必意味着只有一个帧。

它与原生Socket还有重要边界。浏览器脚本不能把WebSocket当成可任意构造TCP首部和连接任意服务的权限;对端需要接受WebSocket握手,并按帧协议交流。服务器明确参与,是这份协议安全模型的一部分。把一段WebSocket字节直接送进只懂普通HTTP或自定义TCP的服务,不会自动变成相容会话。

ws://表示不使用TLS的WebSocket方案,wss://则在其外建立TLS。默认端口分别为80与443,URI的路径等信息用于握手时定位服务。wss的加密能力来自TLS;WebSocket自身的帧掩码只是一种逐帧数据变换,不是保密算法,后面会逐字节解释。

对代理而言,二进制消息可以承载代理请求与目标字节,但两端需要一个适配层:发送方把要传的字节放进消息,接收方去掉WebSocket帧结构,再按代理协议解释。WebSocket不理解SOCKS目标或VLESS身份,也不代替这些协议的认证。它提供承载服务,代理协议提供转发语义。

本章先讨论RFC 6455的HTTP/1.1 Upgrade路径。后续HTTP/2等版本承载WebSocket有另外的规范映射,不能把101响应与Connection字段原样套到所有HTTP版本。通过反向代理或CDN时,也要确认平台是否支持对应升级、持续连接、帧大小及超时,而不是仅因为握手开头看起来像GET就认为必然可用。

10.2 Upgrade 握手:证明对端接受协议,不是证明用户身份 ​

HTTP/1.1路径的客户端先发送GET升级请求。以下使用RFC中的公开示例随机值,只为复核结果,实际连接必须重新随机选择:

text
GET /chat HTTP/1.1\r\n
Host: example.com\r\n
Upgrade: websocket\r\n
Connection: Upgrade\r\n
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n
Sec-WebSocket-Version: 13\r\n
Origin: https://app.example\r\n
\r\n

Upgrade声明希望使用WebSocket;Connection中的Upgrade token使这项升级信息按当前跳处理;Sec-WebSocket-Version的13是WebSocket版本,不是HTTP版本,更不是TLS 1.3。Host和路径则用来选择接收该握手的服务。

Sec-WebSocket-Key是每连接随机选择的十六字节值经Base64后的文本。这类一次使用的随机值常称为nonce;此处它不是保密密钥。服务器按公开规则产生Accept,让客户端确认返回者确实按WebSocket握手处理了请求,而非把普通HTTP响应误当升级成功。

计算步骤是:取Key字段的Base64文本本身,拼接固定字符串 258EAFA5-E914-47DA-95CA-C5AB0DC85B11,对拼接结果作SHA-1,再把二十字节结果Base64编码。SHA-1在这里是一种把输入映射成固定长度输出的公开摘要运算;完整密码学性质后文再讲。关键是不先把Key解码成十六字节再拼接。

上述例子的输出为 s3pPLMBiTxaQ9kYGzzhZRbK+xOo=,服务器成功响应如下:

text
HTTP/1.1 101 Switching Protocols\r\n
Upgrade: websocket\r\n
Connection: Upgrade\r\n
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n
\r\n

客户端要核对101、Upgrade中的websocket、Connection中的Upgrade token,以及根据自己Key算出的Accept;不能只见到HTTP成功类别就开放帧解释器。普通200响应可以是网页或错误入口,不构成RFC 6455握手成功。成功头部结束后,双方才按WebSocket帧处理后续字节。RFC 6455 §4.1—4.2。

Accept不能用于验证服务器的密码学身份,因为任何知道公开Key的人都能算出它;Key也不能当作客户端密码。此流程主要避免无意跨协议接受,要求对端明确参与。若需要确认网站或代理服务身份并保护通道,wss依赖TLS;若要确认用户身份,还需适用的认证机制。

Origin说明浏览器中发起连接的页面来源。服务器可以据此决定是否允许该网页来源使用服务,防止别的站点借助浏览器已附带的身份状态发起不期望的会话。但非浏览器客户端能够自己构造Origin,所以Origin校验不是面对任意客户端的身份认证替代品。浏览器的受控行为与恶意原生客户端,属于不同攻击者模型。

握手还可以协商子协议和扩展。Sec-WebSocket-Protocol选择应用层消息约定;Sec-WebSocket-Extensions协商会改变帧处理的能力。服务器不得凭空选择客户端未提出的子协议或扩展,客户端也不能因自己支持某项功能就在未协商时使用。否则双方对后续保留位和负载的解释会不同。

中间HTTP代理需要正确转接升级语义。有的入口终止握手并另建后端WebSocket,有的把升级后的字节流转送;两者对握手字段、TLS端点和日志的处理不同。若某一跳只返回普通网页,即便GET本身得到了响应,WebSocket仍未建立。反向代理的服务路由、握手验证与长连接转发,需要作为连续路径判断。

10.3 帧头、扩展长度与掩码:从两个字节开始解析 ​

WebSocket帧的基础头占两字节,后面是否追加长度与掩码,由基础头中的位决定。这里的位宽是真正的二进制位,不是字符 0 或 1;图中的高位先标在左侧:

text
第0字节:FIN(1) RSV1(1) RSV2(1) RSV3(1) Opcode(4)
第1字节:MASK(1) Payload-Len(7)
后续:可选扩展长度 → 可选4字节Masking-Key → Payload
字段作用
FIN本帧是否结束当前消息,首帧也可以是末帧
RSV1—3扩展保留位,未协商相应意义时必须为零
Opcode0续帧、1文本、2二进制、8关闭、9Ping、aPong
MASK是否带四字节掩码键
Payload-Len小长度本身,或扩展长度的标志

Payload-Len低七位为0—125时,值本身就是负载字节数;为126时,后接两字节无符号大端长度;为127时,后接八字节大端长度,最高位必须为零。必须使用能表示实际长度的最短形式,不能把五字节负载也故意编码成长长度形式。RFC 6455 §5.2。

实际负载N基础头后的长度字段不含掩码的总头长
0—125无2
126—655352字节4
65536至规定上界8字节10

理论可表示范围不代表实现必须接受巨大帧。接收方要有自身帧、消息和累计分片上限,在申请内存前验证长度,避免整数溢出或资源耗尽。Payload长度计算扩展数据与应用数据,不包括掩码键本身;没有协商扩展时,负载才直接等于本帧应用数据。

客户端到服务器的所有帧必须掩码,服务器到客户端的帧则不得掩码;这些角色规则不因wss已有TLS而取消。客户端每帧选择新的不可预测四字节掩码键,接收方按帧内负载偏移循环使用它。

掩码采用逐位异或,记作⊕:相同位异或得0,不同位得1。若原始负载为P,键为K,则第i字节线上值为:

Ci=Pi⊕Kimod4.

再异或同一个键字节,就恢复P,因为 x⊕k⊕k=x。键本身明文放在帧里,所以这不是保密加密;观察者可执行与接收者相同的恢复。它用于限制不可信客户端应用直接决定线上字节形态,缓解某些中间基础设施把数据误认成其它协议的风险。随机性必须按协议实现,不能因“不是加密”就省略。RFC 6455 §5.3、§10.3。

RFC的文本 Hello 示例使用键 37 fa 21 3d:

i原字节使用键字节异或结果
048,H377f
165,efa9f
26c,l214d
36c,l3d51
46f,o3758

客户端完整帧为:

text
81 85 37 fa 21 3d 7f 9f 4d 51 58

81是FIN=1、RSV全零、Opcode=1;85是MASK=1和长度5。随后四字节键、五字节掩码负载,总长十一。若服务器发送同样内容,完整帧为 81 05 48 65 6c 6c 6f,总长七,没有四字节键。这个公开键只是标准向量,不是实际连接应复用的固定值。

分次读取时,i以本帧负载起点为基准,而非以本次Socket读取起点为基准。若首批恢复两字节,下一批首字节应继续使用K的第2字节;每次读取都从K的第0字节重新开始,会在特定分块下损坏内容。下一帧则重新使用自己的键和i=0,不能把上帧偏移带过去。

解析顺序因此是:收齐基础头并检查角色、操作码及保留位;按标志读取扩展长度并检查上限;需要时收齐键;再在限定范围内恢复负载。不能看见两个头字节就把后面所有缓冲交给应用,因为其中可能仍有长度和键,也可能还包含下一个帧。

10.4 消息分片:续帧维持语义,控制帧可以插入 ​

WebSocket消息可以放在一个FIN=1的数据帧,也可以拆成多个帧。无扩展的基本分片序列以FIN=0的文本或二进制帧开始,中间使用FIN=0、Opcode=0的续帧,最后用FIN=1的续帧结束。文本或二进制类型由首帧决定,续帧不重新选择消息类型。

例如服务器把 Hello 分成 Hel 与 lo,中间插入一个内容为问号的Ping。服务器方向的三个完整帧可以是:

text
01 03 48 65 6c
89 01 3f
80 02 6c 6f
帧字段解释对消息的影响
01 03 ...FIN=0、文本、长度3开始消息并交付或累计Hel
89 01 3fFIN=1、Ping、长度1独立控制事件,不加入文本消息
80 02 ...FIN=1、续帧、长度2加上lo并结束消息

三帧合计十二字节,文本消息仍是五字节 Hello,不是 Hel?lo。接收方必须在处理Ping后保留“已有文本分片尚未结束”的状态,继续接受续帧。把每帧都独立转换成应用消息,或者见到任意FIN=1就结束当前文本消息,都会误把控制帧的FIN作用到另一份状态上。RFC 6455 §5.4—5.5。

基本协议不允许在一个未结束的数据消息中,再用新的文本或二进制首帧开始另一条消息。控制帧可以交错,多个普通消息分片则不能任意交错;只有另有协商且明确定义这种能力的扩展,才能改变相应规则。没有消息进行时出现续帧,也是不合法状态。

这使解析器至少同时维护两类信息:当前帧的长度、键和已消费偏移,以及当前消息的类型、累计大小与是否仍在分片。帧完整不等于消息完整;控制事件可以在大消息尚未完成时处理,却不能把部分帧本身当作边界清楚的控制消息。

文本UTF-8验证也要按完整消息语义处理。字符“你”的三字节 e4 bd a0可以跨文本分片:第一帧只含 e4,续帧包含 bd a0。第一帧单独不是完整UTF-8文本,但累计消息有效。验证器可以流式保留未完成字符状态,到消息结束时确认没有非法或截断序列;不能每帧都无状态地要求独立解码成功。

二进制消息不要求UTF-8,但仍有类型和大小约束。接收端不一定要等全部分片到齐才把任何字节交给上层,能否增量交付由API和应用需求决定;即使增量处理,也不能放弃完整消息长度、分片状态和最终完成判断。无限多个小帧同样可能组成过大的消息,所以只有单帧上限并不能限制全部资源。

控制帧不得分片,负载最多125字节。这个限制便于在一条持续消息之间插入较短控制事件,但它并不让Ping越过已经开始传输的大帧。帧内部不能在任意负载位置塞进另一帧头;接收者仍要先消费当前帧的声明长度,才能识别下一个控制帧。因此大消息适度分片可能改善控制处理时机,实际延迟还受缓存和调度影响。

代理适配层若把多条WebSocket二进制消息恢复成一条内层字节流,还要决定如何处理这些消息边界。目标协议的定界来自它自己的头部和状态,而不该偶然依赖当前WebSocket帧切分。第10.6会把这个映射进一步展开。

10.5 Ping、Pong 与 Close:会话状态不同于业务完成 ​

Ping和Pong为会话提供控制反馈。任一方在会话建立后、关闭之前都可发Ping;收到Ping且尚未收到Close的一方要按规则回应Pong,回应负载应与被回应的Ping一致。规范也允许尚未逐一回应多个Ping时只选择最近处理的一项,以及主动发送无需回应的Pong。RFC 6455 §5.5.2—5.5.3。

例如服务器发送 89 01 3f,表示一字节问号Ping。客户端回应Opcode=a的Pong,负载恢复后仍应是 3f,但客户端线上帧必须使用新的掩码键;不能直接把服务器三个字节改个操作码原样发回。角色的掩码义务也适用于控制帧。

Ping往返说明对应WebSocket端点仍能处理控制消息,不证明代理后的最终网站已经建立连接、通过TLS验证或完成业务。如果反向代理终止了WebSocket,会话的控制反馈也需要按实际终止点解释。它还会经过网络、队列和处理调度,因此测得的往返不是纯传播时间,更不是整条访问中所有目标的统一延迟。

Close则显式结束WebSocket会话。关闭帧Opcode=8、FIN=1,负载可以为空;若有内容,至少两字节为大端状态码,后面可以跟UTF-8原因。只有一字节负载是不合法的,因为无法容纳完整状态码。控制帧最多125字节,所以有状态码时原因最多123个编码后字节,而非123个字符。

服务器表达正常关闭可以发送:

text
88 02 03 e8

03 e8=1000,是正常关闭状态。客户端若用教学键 11 22 33 44 回应同一状态,一个相应帧为:

text
88 82 11 22 33 44 12 ca

掩码后的 12 ca恢复为03 e8。这些键是公开构造值,实际实现仍要每帧新选不可预测值。接收者不能把掩码后的两字节直接解释为关闭状态。

状态代表的协议含义示例
1000正常结束
1002协议错误
1007负载数据与要求不一致,例如非法UTF-8
1008策略约束
1009消息过大
1011服务器遇到阻止完成请求的意外条件

关闭状态也有只能本地表示的保留值。1005表示没有状态值、1006表示异常结束、1015与TLS握手失败相关,这些不能作为Close中的线上状态码发送。日志显示1006,不能据此推断对端真的发来了两字节1006;它常意味着没有取得正常关闭握手。RFC 6455 §7.4。

端点发出Close后不得继续发送数据帧;收到Close且尚未发送Close时,应回应关闭帧。双方都已发送并收到Close后,再结束底层TCP;规范要求服务器立即关闭,客户端通常等待服务器关闭,也可在合理条件下自行关闭。双方同时发起Close同样能够完成这一状态,不需要再无限互相回应。

底层突然断开,与完整Close交换不同。连接复位或传输丢失可能让正在接收的帧或消息截断,应用不能把“没有更多字节”当作前一消息已完整。错误场景下未必还适合发送Close;协议要求失败时应按具体错误状态终止,不能在已经判定失败后继续尝试解释不可信后续数据。

WebSocket关闭也不直接等同于TCP的独立半关闭服务。基础Close针对会话结束,不提供一个通用“我已结束发送业务、但继续等待任意长返回”的代理命令。如果内层代理需要精确保留方向结束语义,适配层必须明确怎样表达,不能把Close未经分析就当成所有内层EOF的等价映射。

10.6 从二进制消息恢复代理字节流 ​

WebSocket向应用提供消息,许多代理协议却需要一条连续字节流。把二者连接起来的适配层,需要约定“怎样把字节放进消息,以及怎样从消息恢复字节”,而不是让WebSocket自动识别代理请求。本节讨论一种常见的流映射模型,不把它当成RFC强制的代理功能。

设发送者依次产生字节序列S,把它分成若干二进制消息M1、M2……,接收适配层按顺序把各消息的应用数据串接,恢复 S=M1∥M2∥…。WebSocket负责帧与消息完整性,适配层负责这个串接契约;内层代理解析器仍依据自己的字段定界。

沿用第3章十三字节教学流:

text
00 05 48 45 4c 4c 4f 00 04 ff 00 01 0a

它含一条五字节 HELLO 和一条四字节二进制内容,各自以两字节长度前缀定界。可以把前八字节放入第一条WebSocket二进制消息,后五字节放入第二条:

WebSocket消息应用数据内层解析结果
M1,8字节00 05 48 45 4c 4c 4f 00第一条内层消息完整;保留下一长度前缀的首个00
M2,5字节04 ff 00 01 0a与已保留00组合出长度4,恢复第二条内容

若接收方错误地要求每条WebSocket消息恰好容纳一条完整内层请求,M1尾部会被判成垃圾、M2开头会被误当新头。两端可以另行设计消息对齐约定,但那是附加协议;在本文的流映射模型里,内层边界不依赖外层消息大小。

每条消息又能分成多个WebSocket帧,底层TCP还可以把帧任意拆分读取。完整恢复路径因而是TCP累计字节→WebSocket帧解析与去掩码→消息状态处理→适配后的内层字节流→代理请求解析。若有协商压缩,还要在正确位置解码扩展。任何一层都不能用自己的读取次数代替下一层的消息边界。

选择二进制消息的直接好处是允许任意字节,不必把密钥材料或目标数据强行解释成UTF-8。若改用文本消息承载Base64,两端必须明确约定解码,而且会增加编码量与处理;WebSocket本身并不要求所有二进制负载都先Base64。无理由的文本转换既不增加保密,也可能破坏原始字节。

面向数据报的映射可以采用另一种契约,例如一条二进制消息对应一个内层数据报,并在内部保留必要目标信息。此时不能无条件把所有消息串接,否则丢失报文边界。相同WebSocket承载可以服务流或数据报,但必须由上层协议定义,不能从“二进制消息”四字推断业务语义。

适配还涉及缓存。消息API可能先把完整消息排进用户态队列,再异步写向网络;反方向可能要求完整消息到齐才交付。它们不一定像一个简单TCP读写循环那样自然传递背压。实现需要限制单帧、单消息、未发送队列和累计内层输入,并定义关闭时怎样处理尚未交付内容。

安全边界同样取决于终止点。如果反向代理只转送升级后的字节,最终服务解释WebSocket;如果它自己终止WebSocket再生成另一条后端会话,它便能取得本层应用负载。外部wss只保护其TLS端点之间;里面若还有网站TLS或独立代理加密,就要继续追踪内层保护,而不能由一个wss地址推断所有节点都看不到业务。

实际Xray的WebSocket包装器怎样把消息映射成流,将在第20章结合固定源码定位。本节先建立正确的分析问题,避免把特定库的消息大小、写入策略和关闭行为当成WebSocket标准本身。

10.7 帧开销怎样随长度变化 ​

没有扩展时,WebSocket每帧线上长度等于负载N加头部h。h由基础头、扩展长度和客户端掩码键组成,不包含HTTP握手、TLS记录、TCP/IP或链路开销。令e(N)在N≤125时为0,126—65535时为2,更大合法长度时为8,则:

h服务器(N)=2+e(N),h客户端(N)=6+e(N).
负载N,字节客户端头长客户端总长服务器头长服务器总长
06622
561127
12561312127
12681344130
65535865543465539
6553614655501065546

零长度客户端帧仍有四字节键,不能因没有负载就省略。表格里的大长度只展示格式阈值,实际端点可以设置远小于理论范围的上限。

一个完整三字节二进制消息 abc,客户端使用教学键 11 22 33 44,可构造:

text
82 83 11 22 33 44 70 40 50

82表示单帧完成的二进制消息,83为带掩码的三字节长度;70 40 50依次恢复为 61 62 63。服务器发送同样二进制消息为 82 03 61 62 63。二者分别九字节与五字节,差别就是客户端方向的四字节键。

长度标记也有跳变。126字节的服务器二进制帧,头部是 82 7e 00 7e,后面才有126字节负载;65536字节时,头部为 82 7f 00 00 00 00 00 01 00 00,后面才接负载。这两行仅展示完整头部,不冒充已经列出整帧内容。扩展长度按大端计算,并且不能用127形式表达本可用126形式容纳的长度。

对于五字节客户端消息,单帧头相对负载是 6/5=120%;对16 KiB负载,头为八字节,相对开销约 8/16384=0.0488%。同一协议对极小消息与大块传送呈现的比例非常不同,不应把一个数字当作所有负载的固定开销率。

主动分片会增加头部次数,但也可能改变长度档位。126字节若在客户端拆成两个63字节帧,头总长十二字节,比一个126字节帧的八字节多四;在服务器方向,两个两字节头刚好与一个四字节头一样大。若要比较更复杂分片,必须对每帧重新选e(N),而非简单用最大头长乘帧数。

分片还影响处理时机与缓存,而不只是字节数。很大的帧可以摊薄头部,却让控制帧需要等待当前帧传完;许多很小帧增加解析、调度和掩码键生成次数。哪种更合适取决于写入模式、端点API和控制延迟需求,没有一个帧大小能无条件最优。

掩码对负载逐字节变换,实现可以批量或向量化处理,是否额外分配与复制又取决于缓冲设计。协议规定了线上结果,不规定必须调用多少次系统调用、复制多少遍内存。把“需要掩码”直接换算成一个固定CPU百分比,或者把一帧等同于一次send,都是缺少实现和负载条件的推断。

因此本表只给出可由格式精确推出的成本。握手等待、TLS处理、网络拥塞与实际吞吐要在完整路径里继续计算。第30章会用相同计量原则把不同代理与承载放到可比较的条件下。

10.8 扩展、压缩与中间设备:同一种帧结构还有协商条件 ​

WebSocket基础头留出RSV位,但这些位不是应用可以任意使用的私人空间。非零值需要握手中已经协商的扩展来定义;接收方不认识其含义时要按协议失败,不能忽略一切保留位后继续把负载交给代理解析器。

一个实际例子是RFC 7692的 permessage-deflate。它以消息为单位应用DEFLATE压缩,用RSV1标记消息经过该扩展处理;消息分片时,标记位在首个数据帧上,续帧按已经确定的消息状态解释。控制帧不因此变成压缩消息的一部分。压缩在消息与帧负载之间引入新步骤,不能等同于HTTP的Content-Encoding字段。RFC 7692 §7。

DEFLATE能够利用先前出现的字节模式,用较短的表示引用重复内容。编码器与解码器因而维护历史窗口。扩展可以协商是否在消息之间保留这份历史,称为context takeover;也可以约束窗口大小。保留历史可能提高重复消息的压缩效果,却需要跨消息状态与相应内存。关闭上下文接管,则让新消息不再依赖前一个消息的历史。

参数还区分方向。server_no_context_takeover限制服务器发送的新压缩消息如何初始化历史;client_no_context_takeover限制客户端方向。应按最终接受的参数判断,不能只因为客户端提议中出现某项,就假定协商结果已经包含它。接收方的解压状态必须与对方实际允许使用的历史一致。

这份扩展也不是把一个完整gzip文件塞进负载那么简单。RFC 7692规定压缩消息结尾的特殊处理:发送表示中移除相应尾部四字节 00 00 ff ff,接收者在消息结束时按规范补回,再执行DEFLATE解码。分片中的非末部分不能各自重复做消息结尾操作。掩码应用于实际帧负载,接收端先去掩码,再按扩展恢复消息数据。RFC 7692 §7.2。

所以10.7的N指线上帧负载长度:启用扩展后,它未必等于原始业务内容长度。解压后的消息还应有独立上限;只限制一帧输入大小,不能阻止一个短压缩输入产生很大输出。压缩也需要CPU和状态,不能仅看节省的运输字节。

是否值得压缩还依赖内容。重复文本可能从历史引用受益,已经压缩的文件或经过良好加密的记录通常没有同样的可利用冗余;具体收益必须在负载条件下判断。若受攻击者影响的数据与秘密共享压缩上下文,压缩后长度还可能成为侧信道,安全传输本身并不消除这个问题。RFC 7692 §8明确提醒压缩与安全通道组合的相关风险。

中间设备兼容性又与这些端点功能分开。反向代理需要正确支持升级及升级后的长连接;它可能配置空闲超时、帧或消息大小限制,也可能在终止后用另一套参数建立后端会话。HTTP握手成功只确认当前会话被接受,不证明某个业务的所有尺寸、持续时间和扩展组合都已兼容。

WebSocket因此适合提供一种受控双向消息承载,但不承诺不可识别、无限长寿命或无额外成本。握手路径、固定URI、方向掩码和控制节奏都可能是可观察特征;是否适合某条代理路径,应结合端点、网关与内层协议分析,而不是把“HTTP升级”理解成任何网络环境都接受的保证。

本章的基础机制依 RFC 6455 §4—7、§10,压缩扩展依 RFC 7692 §7—8。后面HTTP/2会提供不同的多流承载,理解它之前,必须继续保留本章建立的区别:消息类型不等于帧类型,扩展解码不等于应用解析,外层安全也不等于所有终止节点都无法读取负载。

从字节与状态机出发,理解每一层的职责。