在面试和日常开发中,“跨域”与“实时通信”是绕不开的两座大山。最近在复习相关知识,顺便写了一个 Node.js + 原生前端的 WebSocket 实战 Demo。本文将从跨域方案盘点到 HTTP 协议的限制,再到 WebSocket 双工通信的代码落地,希望能帮你彻底理清这些概念。
一、 跨域方案大盘点
在浏览器中,由于同源策略(协议、域名、端口不同即视为跨域),前端发起请求时经常会遇到跨域拦截。笔记中梳理了以下几种常见的解决思路:
- Nginx 反向代理:前端项目部署
index.html,前端发送的请求统一走/api,通过 Nginx 反向代理到真实的:3001/服务器。这也是生产环境最常用的方案。 - Vite + MockJS:利用 Vite 的开发服务器代理,在本地开发时进行接口 Mock。
- CORS(跨域资源共享) :服务端设置
Access-Control-Allow-Origin等响应头,属于真正的跨域资源共享。 - JSONP:利用
<script>标签没有跨域限制的特性,通过json with padding实现,但只支持 GET 请求。 - postMessage:主要用于页面之间的跨域通信(如 iframe 与父页面)。
- WebSocket:WebSocket 协议本身不受同源策略限制,原生支持跨域通信。这是因为 HTTP/S 协议受同源策略约束,需要浏览器拦截跨域请求,而 WebSocket 在建立连接时是通过 HTTP 协议升级(101 Switching Protocols)实现的,后续通信不再受同源策略的限制。
二、 HTTP 的局限与实时通信的演进
传统的 HTTP 协议是单向传输的:用户发送请求,服务器反馈完成就断开(短连接)。一般情况下,服务器是不可以主动向用户发送数据的。
为了实现“服务器主动推送”,技术经历了以下演进:
1. SSE(Server-Sent Events)
SSE 是一种单向流式输出技术,服务器可以不断向浏览器推送数据。
核心响应头:
http
Content-Type: text/event-stream;
Cache-control: no-cache;
Connection: keep-alive;
面试思考:既然 WebSocket 是全双工通信,为什么大模型(LLM)的流式输出往往使用 SSE 而不是 WebSocket?
解答:大模型的流式输出通常是“单边生成,单边输出”,SSE 基于 HTTP,实现更轻量,断线重连机制更简单。虽然 WebSocket 也可以做到一边生成一边输出(双向也是可以的),但如果不是为了实时双向交互(如聊天室、直播弹幕),SSE 是更经济的选择。
2. WebSocket(全双工通信)
像 QQ、微信等即时通讯,不再是 HTTP 那种“只有浏览器发数据,服务器才能回”的模式。WebSocket 允许服务器和客户端两边都可以发送数据,地位平等。
应用场景包括:在线状态同步、实时聊天、直播弹幕等。
连接建立过程(分两步):
- 先通过 HTTP 连接服务器(
http://localhost:8080),此时是一次普通的 HTTP 请求。 - 发送
101 Status Code(Switching Protocols),协议升级为 WebSocket 协议。基于 HTTP Web Server 的 Socket 服务,双向通信就建立了。
补充:HTTP 状态码 1XX 表示还在通信中(没有完成),2XX 成功,3XX 跳转,4XX 用户错误,5XX 服务器错误。
三、 Node.js WebSocket 服务端实战
下面结合代码,看看如何基于 Node.js 的 ws 库搭建 WebSocket 服务。注意代码中的每一行注释,都是核心知识点。
javascript
// commonjs 老的,esm 新的
// module
// html5 新增的功能
const WebSocket = require('ws');
const http = require('http'); // node 内置的 http 模块
// 先要把 http server 启动
const server = http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/plain',
});
res.end('WebSocket Server Running');
});
// 基于 http server 再搭建 socket 协议
// WebSocket
const wss = new WebSocket.Server({ server, path: '/ws' });
// 监听事件 有人连接
wss.on('connection', (ws) => {
console.log('Client Connected');
// 监听客户端发来的消息
ws.on('message', (message) => {
console.log('收到消息', message);
// 给客户端发送消息
ws.send(`Server received: ${message}`);
});
});
server.listen(8080, () => {
console.log('WebSocket Server Running on http://localhost:8080');
});
代码解析:这里非常巧妙的一点是,
WebSocket.Server挂载到了原生的http实例上({ server, path: '/ws' })。这意味着我们不需要额外占用端口,HTTP 和 WebSocket 共用 8080 端口,通过路径/ws进行区分。
四、 前端 WebSocket 客户端实现
前端原生就支持 WebSocket API(HTML5 新增功能)。需要注意的是,连接地址的协议头要从 http:// 改为 ws://,如果是 HTTPS 则对应 wss://。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>WebSocket Client</h1>
<script>
// html5 支持 WebSocket 协议
// http:// -> ws://
const ws = new WebSocket('ws://localhost:8080/ws');
// 连接成功
ws.onopen = () => {
console.log('连接成功');
ws.send('Hello From Client!');
}
// 收到服务器消息
ws.onmessage = (e) => {
console.log(`收到消息:${e.data}`);
}
// 连接错误
ws.onerror = (e) => {
console.log('连接错误', e);
}
// 连接关闭
ws.onclose = () => {
console.log('连接关闭');
}
</script>
</body>
</html>
五、 总结
- 跨域限制:常规 HTTP 请求受同源策略限制,需要 Nginx、CORS 等手段解决;而 WebSocket 协议不需要遵守同源策略,原生支持跨域通信。
- 协议选择:SSE 适合服务器单向推送(如 LLM 流式输出、新闻推送);WebSocket 适合双向高频交互(如聊天室、协同编辑、游戏)。
- 连接本质:WebSocket 建立在 HTTP 之上,通过 101 状态码升级协议,底层复用了 HTTP Server。
希望这篇笔记能帮你在面试或实际开发中,更清晰地把握前端网络通信的脉络。如果觉得有帮助,欢迎点赞收藏!