别把 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。两端参数结构不同,是接口设计差异,不是写错。

一次完整往返:onopen 发 hellow 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、前端、面试

相关推荐
Knight_AL13 小时前
基于 Netty 实现的 WebSocket 服务端
网络·websocket·网络协议
幸运小圣17 小时前
Buffer 新手入门【Node.js】
node.js·编辑器·vim
sjg2001041417 小时前
websocket基本使用
网络·websocket
jj_ccwgw20 小时前
从 0 到 1 搭建 pnpm + Workspaces + Turborepo Monorepo 保姆级指南
前端·node.js
脉动数据行情121 小时前
Go 语言实战|对接美股英伟达 NVDA 实时行情 生产级 WebSocket 方案
开发语言·websocket·golang
梦想平凡21 小时前
棋牌游戏技术实战(一):用 Node.js 实现房间消息队列、请求幂等与断线恢复
游戏·node.js
小天源1 天前
GEO 品牌监测系统实战:基于 Node.js + Playwright 实现多平台采集与报告导出
人工智能·node.js·geo·品牌检测·ai诊断
damoluomu1 天前
Directus 换掉 GPL 之后,Node.js CMS 到底怎么选?(一)
node.js
guslegend2 天前
脚手架入门:必要性、核心功能与执行原理
前端·架构·node.js·脚手架·前端工程化
HaipengYu3 天前
深度解密 OpenCode 2 插件加载机制:Dual-Version (V1/V2) 适配实战与官方文档的“隐形陷阱”
node.js