别把 WebSocket 当成一门新协议学:搞懂"借 HTTP 握手",双端 Demo 和跨域就都通了

别把 WebSocket 当成一门新协议学:搞懂"借 HTTP 握手",双端 Demo 和跨域就都通了

想让服务器主动推消息?先会遇到三个选择:轮询、SSE、WebSocket。轮询全是无效请求;SSE 只能服务器到浏览器单向输出(AI 流式回答够用);要做聊天、在线状态这种双向实时,答案才是 WebSocket。本文的判断是:WebSocket 的关键不是背 API,而是理解它"借一次 HTTP 握手(101)升级为双工长连接"这一个动作 ------理解了它,为什么能复用 8080 端口、为什么能跨域,全部顺理成章。材料来自一份学习笔记与最小双端 Demo,代码静态整理、运行未验证,依赖需 npm i ws

先看握手:一个 HTTP 请求的"变身"

笔记里记的两步是全部核心:先由 http 连接找到 web server,再靠 101 Switching Protocols 升级协议(readme.md L53-56)。握手请求只比普通请求多几个头:

http 复制代码
GET /chat HTTP/1.1
Upgrade: websocket          // 我想升级协议
Connection: Upgrade         // 别当普通请求处理
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==   // RFC 6455 示例值

背景是 HTTP 的单向性:一问一答,服务器一般不能主动发数据(readme.md L37-39)。而 WebSocket 是 HTTP 之外的双工协议,两端平等、随时互发,QQ/微信式实时聊天靠的就是它(readme.md L41-44)。

服务端:一台 HTTP 服务器,两种协议

demo/server.js(CommonJS)完整骨架:

js 复制代码
const WebSocket = require('ws');   // 第三方 WebSocket 库
const http = require('http');      // Node 内置 http 模块

const server = http.createServer((req, res) => {
  res.writeHead(200, {'Content-Type': 'text/plain'}); // 状态码 + 响应头
  res.end('WebSocket Server Running');                // 响应体 + 结束
});

const wss = new WebSocket.Server({ server, path: '/ws' });
wss.on('connection', (ws) => {
  console.log('Client connected');
  ws.on('message', (msg) => {
    console.log(`Received: ${msg}`)   // msg 直接是消息内容(Buffer)
  })
  ws.send('你好')                     // 服务器→客户端
});

server.listen(8080, () => {
  console.log('WebSocket Server Running on port 8080')
})

这份代码里最值钱的三个设计:

  • 复用 + 分流WebSocket.Server({ server, path: '/ws' }) 借用上面那台 HTTP 服务器收握手请求,只接管 /ws 路径------普通请求走 createServer,升级请求归 wss,一个 8080 端口两种协议。
  • 嵌套的作用域原因ws 是"这个客户端"的专属连接对象,只在 connection 回调里存在;所以 message 监听必须嵌套在里面。先知道谁来了,才能监听他说话。
  • createServer 不等于开门server.listen(8080) 之后端口才监听;随后进程不退出,进入事件等待------服务器 99% 的时间在等事件。

客户端:异步握手决定代码顺序

demo/index.html 脚本部分:

js 复制代码
const ws = new WebSocket('ws://localhost:8080/ws') // 连接服务器
ws.onopen = () => {
  console.log('连接成功')
  ws.send('hellow from client') // 客户端→服务器
}
ws.onmessage = (e) => {
  console.log(`Received: ${e.data}`)
}
ws.onerror = (e) => { console.log(e) }
ws.onclose = () => { console.log('disconnect from server') }

材料里标注的一个细节值得单独说(index.html L20 注释):浏览器 onmessage 收到的必须是 MessageEvent 对象 ,消息在 .data;而 ws 库的 message 直接给内容(Buffer),模板字符串会隐式 toString。两端参数结构不同,是接口设计差异,不是写错。

一次完整往返:onopenhellow from client → 服务端打印 → 服务端回 你好onmessage 打印。双端事件对应:

浏览器端 服务器端 触发时机
onopen connection 握手完成(同时触发)
ws.send() message 事件 对端发送时
onmessage ws.send() 对端发送时
ws.close() close 事件 一端挂断

跨域:同源策略管不到 WebSocket 的握手

同源策略的本质:跨域请求其实已经发出,是浏览器拦截了响应读取;服务器之间通信不受限。笔记原文(readme.md L64-67):同源策略会拦截跨域请求,但不限制 WebSocket 的跨域通信------不过 WebSocket 主要用于实时交流,不会被拿来当常规跨域手段。

由此整理跨域方案全景(nginx 部分摘自 readme.md L3-18:80 端口返回 index.html,拦截 /api 转发 3001,浏览器全程同源):

方案 思路 场景
nginx 反向代理 让请求不跨域 生产部署
vite proxy 同上,开发服务器兼任 nginx 本地开发
CORS 后端声明放行;非简单请求先发 OPTIONS 预检 前后端分离主流
JSONP <script> 不受限的空子,只支持 GET 已过时,答原理
WebSocket 握手不受同源策略限制 实时聊天、推送
postMessage 窗口/iframe 间通信 嵌第三方页面

顺手记一组状态码(readme.md L57-61):1xx 通信中(101 升级)、2xx 成功、3xx 重定向、4xx 前端错、5xx 后端错。

收藏:跑通前的自检清单

  • npm i ws 已执行(demo 未附带依赖清单)
  • server.listen(8080) 存在,回调打印启动日志
  • 客户端 URL 路径与 path: '/ws' 一致
  • send 放在 onopen
  • 浏览器取 e.data,服务端直接用 msg
  • 想双向对话:服务端 message 回调里补 ws.send(...)

留一个可立即执行的检查

可迁移的判断:WebSocket 不是平行于 HTTP 的另一套世界,而是 HTTP 连接的一次"升级",并且升级请求可以和普通 HTTP 共用同一台服务器、按路径分流 ------这也是它天然跨域的根源(同源策略只拦 HTTP 响应读取)。现在就做一件事:打开浏览器 DevTools 的 Network 面板,筛选 WS 类型刷新页面,你会亲眼看到那条 101 的握手记录;若没看到,按上面清单检查路径是否为 /ws。下一步实验方向:给 demo 加"群聊广播",体会 connection 里每个 ws 的专属语义。

标签:WebSocket、Node.js、前端、面试

相关推荐
shmily麻瓜小菜鸡4 小时前
TDD(测试驱动开发)详解
开发语言·javascript·typescript·node.js·ecmascript
半生过往6 小时前
Node.js 多版本管理完全指南
node.js
梦帮科技18 小时前
AI 音乐产品的发布工程:验证门、数据发布、回滚与生产运维纪律
数据结构·数据库·架构·node.js·音视频·动态规划·推荐算法
65岁退休Coder1 天前
把 Agent 框架拆开:PI 开发生产级 Harness
后端·node.js·agent
梦想平凡1 天前
情怀棋牌源代码焕新记录(一):全新国风UI与原版工程梳理
java·服务器·数据库·websocket·网络协议·cocos2d
李昊哲小课2 天前
Spring Boot SSE 实时推送教程
spring boot·websocket·flux·sseemitter
65岁退休Coder2 天前
PI Agent 开发一个生产级 Harness
后端·node.js·agent
szephyr2 天前
WebSocket 实战:心跳、断线重连、鉴权,一次讲清
前端·websocket·node.js·长连接·实时通信
秋秋小事2 天前
node postgreSQL的select与include
node.js