俗话说得好:“面试造火箭,工作拧螺丝”。但如果你连长连接的底层协议都搞不清楚,可能连进大厂拧螺丝的资格都没有。
昨天,隔壁寝室的哥们面字节暑期实习,直接被一道 408 场景题干得汗流浃背: “做过 Chat App 是吧?那你说说 WebSocket 和 SSE 有什么区别?接 DeepSeek 的流式输出该用哪个?”
很多同学平时写业务天天 npm install 调包,遇到网络层的问题直接“阿巴阿巴”。但在这个 AI 大模型全网刷屏的时代,长连接和流式输出早就成了前端和 Node.js 圈的绝对高频考点。
作为一名见不得“屎山代码”的大三党,今天学弟就带大家抓个包,把 HTTP 轮询、WebSocket 和 SSE 的底层逻辑扒个底朝天。建议先 ⭐ 收藏,面试前拿出来背一遍,绝对让面试官对你刮目相看!
🤡 为什么说 HTTP 轮询是“外包级”方案?
假设现在需求是做一个在线聊天室。新手最爱干的事,就是写个 setInterval(),每隔 3 秒发个 Ajax 请求去问服务器:“大佬,有新消息吗?”
⚠️ 前方高能:这是典型的史诗级灾难写法! HTTP 是一个无状态、单向的短连接(Request-Response 模型)。你每次轮询,都要重新建立 TCP 连接(即使有 Keep-Alive 也会有巨大开销),还要带上一大堆臃肿的 HTTP Header。
打个通俗的比方:HTTP 就像是**“寄信”**。用轮询做聊天室,就像是你每隔 3 秒就去狂敲邮局的门问:“有我的信吗?”——不仅你累,服务器也得被你烦死,人一多直接原地宕机。
🚀 降维打击:WebSocket 的全双工魔法
为了终结这种愚蠢的轮询,HTML5 推出了 WebSocket 协议。这玩意儿一上来,直接把“寄信”跨时代地升级成了**“打电话”**。只要电话一接通,双方就可以毫无阻碍地互发消息。
Talk is cheap,我们先看一眼用 Koa 撸一个 WebSocket 服务器有多优雅:
JavaScript
const Koa = require('koa');
const websocket = require('koa-websocket');
// 注入 WebSocket 能力
const app = websocket(new Koa());
const clients = new Set(); // 维护客户端连接池
// 处理 WebSocket 长连接逻辑
app.ws.use(async (ctx, next) => {
clients.add(ctx.websocket); // 用户上线
// 服务端接收到消息时,广播给所有人(群聊核心逻辑)
ctx.websocket.on('message', message => {
for (const client of clients) {
client.send(message.toString());
}
})
// 划重点:断开连接时必须清理内存,否则会导致内存泄漏!
ctx.websocket.on('close', () => {
clients.delete(ctx.websocket);
})
})
app.listen(3000);
代码很简单,但面试官真正在意的是下面这两个底层护城河:
💀 硬核揭秘 1:抓包看 101 协议升级 的密码学验证
面试官发难:“WebSocket 建立连接时发的是 HTTP 请求吗?”
拔掉网线,打开 Wireshark 或者 Network 面板抓个包,你会发现第一次握手的 Header 里藏着玄机:
HTTP
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
看到这个 Sec-WebSocket-Key 了吗?服务端收到这串随机的 Base64 字符后,必须做一套极其严格的规范动作:
- 把这个 Key 与一个全球通用的魔法字符串(
258EAFA5-E914-47DA-95CA-C5AB0DC85B11)拼接。 - 进行
SHA-1运算,再转成 Base64,生成Sec-WebSocket-Accept返回给客户端。
为什么要这么折腾?防黑客吗? 错!明文传输防个锤子。这是为了防止无意的**“缓存投毒” (Cache Poisoning)**,并且让客户端确认:“对面这台服务器是真的懂 WebSocket 协议,而不是碰巧返回了 200 OK”。
💀 硬核揭秘 2:为什么 WS 能发图片,而 HTTP 只能发文本?
WebSocket 传输的数据不叫报文,叫**“数据帧(Frame)”**。协议底层定义了一个 4 bit 的 Opcode(操作码) :
Opcode = 0x1:浏览器知道这是一串文本。Opcode = 0x2:浏览器知道这是一坨二进制流,直接扔给 ArrayBuffer 处理图片或音视频。
这才是它能扛起复杂互动场景(如页游、直播弹幕)的全能底气。
🤖 大模型时代的新宠:SSE (Server-Sent Events)
既然 WebSocket 这么强,那为什么我们用 ChatGPT 或 DeepSeek 时,抓包发现它们根本没用 WebSocket,而是用了 SSE?
因为业务场景变了! 大模型的“打字机效果”,是一个单向流式输出的过程。你发一句 Prompt,AI 连续吐出几百个词。这个场景根本不需要全双工双向发消息,只需要服务器单向高频推送即可!
💀 硬核揭秘 3:扒掉 SSE 的外衣,它的底层其实是 Chunked 编码
很多小白把 SSE 当成什么高深的新协议,大错特错!SSE 是 100% 纯正的 HTTP/1.1 协议。
它的核心黑科技,是利用了 HTTP 响应头里的 Transfer-Encoding: chunked(分块传输编码) :
HTTP
Content-Type: text/event-stream
Transfer-Encoding: chunked
Connection: keep-alive
正常的 HTTP 请求必须带 Content-Length,浏览器拿到指定大小的数据就关门大吉。 但加上 chunked 后,服务器的意思是:“我也不知道 AI 要说多少废话,我一块一块(Chunk)发给你吧。”
服务器每次吐出一个字,就按 data: 你好\n\n 的格式发过去。浏览器底层的流处理器只要读到 \n\n,就知道一块数据到了,立刻触发前端的渲染。杀鸡焉用牛刀,处理单向推送,SSE 才是最优雅的神!
🔥 终极避坑:大厂必问的“心跳保活”机制
不管你用 WS 还是 SSE,只要写了“长连接”,面试官必放终极杀招: “如果用户进了电梯没信号了,或者直接拔了网线,你的服务器怎么知道他掉线了?”
千万别回答“等 TCP 超时断开”——TCP 底层的 Keep-Alive 默认要两小时才触发,那时候你服务器的连接池早被死链接撑爆了!
正确的做法是在应用层实现心跳机制 (Heartbeat) :
- 常规玩法:客户端定时器每隔 30 秒发一个 JSON 格式的 Ping 消息,服务器回复 Pong。超时未收到回复,前端主动断开并重连。
- 满分玩法(针对 WebSocket) :利用刚才提到的底层帧结构!WebSocket 协议原生定义了
0x9 (Ping帧)和0xA (Pong帧)。在 Node.js 中,你可以直接调用底层的 Ping/Pong 控制帧,连 JSON 序列化的性能损耗都省了,把并发性能压榨到极致!
🎯 总结:没有银弹,只有取舍
架构设计的魅力就在于“看菜下饭”:
- 做 联机游戏、协同文档、实时聊天室 👉 毫不犹豫选 WebSocket。
- 做 大模型对话、站内单向消息通知 👉 选轻量级、原生兼容 HTTP 的 SSE。
技术迭代浩浩荡荡,最后给各位技术大佬留个探讨题:随着 HTTP/2 和 HTTP/3 的普及,它们强大的多路复用和全双工特性,未来会让 WebSocket 退出历史舞台吗?
欢迎在评论区畅所欲言,学弟在线挨打交流!👇