做前端或者客户端开发的人,大概率都踩过WebSocket的坑。我最早接触wss是在一个实时消息项目里,本来以为WebSocket就是个“长连接”,比轮询高端,结果一上线就各种问题——连接时不时断开、消息延迟、服务端收不到某些帧、高版本浏览器直接连不上。当时就意识到,如果只是会调API而不懂ws和wss的底层机制,出了问题真的无从下手。
这篇东西不打算教你调某个框架的API,而是想分享一套分析WebSocket/WSS连接的完整方法:从握手开始,到抓包定位,到帧结构,再到线上问题的排查链路。适合那些已经能跑通WebSocket Demo,但在真实项目里遇到连接不稳、消息延迟、协议细节搞不清楚的开发者。文章里的内容全部来自实际项目里的排查经验,不是理论堆砌。
1. WSS连接从建立到关闭:一次握手里面的信息量比你想的多
1.1 先搞清楚WebSocket解决了什么
很多人对WebSocket的理解停留在“服务端可以主动给客户端发消息”,这个说法没错,但不够本质。HTTP是半双工协议,客户端发一个请求,服务端给一个响应,这个循环一旦结束,连接就断开了。服务端想主动推送数据,只能靠轮询或者长轮询模拟——客户端反复问“有新消息吗”,服务端反复回答“没有”或者“来了”。
WebSocket的本质是:通过一次HTTP升级握手,把连接从HTTP协议切换成一个全双工的、基于TCP的二进制帧协议。握手完之后,客户端和服务端可以同时给对方发数据,不需要再协商“谁先说话”。这个切换是不可逆的,握手成功之后,这条TCP连接上跑的就不再是HTTP报文了。
打个不严谨但好懂的比方:HTTP像是对讲机,按下才说话,说完得等对方回;WebSocket像电话线,两边随时都能说,还能同时说。wss就是这条电话线外面再加了一层TLS加密隧道,相当于电话线路本身做了加密传输。
1.2 握手请求的字段里藏着哪些决定性信息
WebSocket的握手不是另起炉灶,它就是一个带了特殊头部的HTTP GET请求。客户端发给服务端的握手请求长这样:
code复制GET /ws HTTP/1.1
Host: api.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Version: 13
Origin: https://example.com
这里面最容易被忽略的是Sec-WebSocket-Key。这串Base64并不是什么鉴权凭证,它只是一个随机数,作用是让服务端验证“你确实是WebSocket服务端”。服务端拿到这个Key之后,会把它跟一个固定的GUID拼接,做SHA-1哈希,再做Base64编码,生成Sec-WebSocket-Accept返回给客户端:
code复制HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
固定GUID是258EAFA5-E914-47DA-95CA-C5AB0DC85B11,这是RFC 6455里写死的字符串。客户端收到响应后,会自己用同样的算法算一遍Sec-WebSocket-Accept,如果对不上,直接断开连接。这个机制防止了某些中间层误把WebSocket请求当成普通HTTP转发,实际上相当于一次轻量级的协议握手校验。
排查连接问题时,第一步永远是看握手结果。如果状态码不是101,说明连接压根没建立起来;如果握手响应头里少了Sec-WebSocket-Accept,说明服务端可能没有实现规范,只是随手返回了一个200或者400。
1.3 WSS的本质:在TLS隧道里跑WS协议
wss和ws的区别,一句话就能说清:wss = WS over TLS。也就是说,TCP连接建立之后,先走一遍TLS握手,协商出对称加密密钥,之后所有的WebSocket帧都在这条TLS隧道里传输。
这就带来一个调试上的问题:你抓包看到的全是TLS密文,看不到里面的WebSocket帧。浏览器开发者工具里能看到WebSocket的Message内容,是因为浏览器在内存里已经解密了。如果你想在网络层直接查看wss流量,要么让抓包工具成为TLS的中间人(代理模式),要么把TLS会话密钥导出给Wiresh
