服务器本身不能“凭空”把数据塞给客户端,必须借助客户端主动建立的连接通道,通过HTTP轮询、Server-Sent Events (SSE) 或 WebSocket 这三种主流技术来实现。 这就像快递员没法直接进你家,必须你打开门或者留个窗口,他才能把包裹递进来,下文将详细拆解这三种技术的原理、适用场景和具体操作,帮你彻底搞懂服务器怎样向客户端发送消息。
服务器推送消息给客户端:HTTP轮询的笨办法与聪明变体
最早期的网页应用,服务器想给客户端发消息,只能靠客户端“厚着脸皮”反复去问,这就是HTTP轮询,也是从业者常说的“短轮询”。
短轮询的运作机制和痛点
短轮询的流程非常直接:客户端通过JavaScript的setInterval函数,每隔几秒就向服务器发一个普通的GET请求,问“有我的新消息吗?”服务器每次都得认真回复,哪怕回答是“没有”。
- 核心步骤:客户端设置定时器 -> 发起HTTP请求 -> 服务器查询数据库或内存 -> 返回响应(有数据则带数据,无数据则空响应)-> 客户端处理并关闭连接。
- 资源浪费明显:在无消息期间,每一次请求都是完全无效的网络开销,据行业共识,在高并发场景下,超过80%的轮询请求是无效且消耗性能的,这种模式对服务器带宽和数据库压力都很大。
长轮询:轮询的进化版
为了解决短轮询的浪费,长轮询出现了,它的核心改进点是:服务器不立即回复“没有”。
- 工作原理:客户端发请求后,服务器把这次连接挂起(Hold住),保持连接不关闭,等待有数据时再返回,如果超过设定时间(比如30秒)没数据,就返回一个超时响应,客户端收到后立即再次发起请求。
- 实际体验:和短轮询相比,长轮询的实时性有提升,消息平均延迟大约能降低一个请求周期,但每次请求仍要携带HTTP头,连接频繁建立和销毁,在移动网络下会尤为明显。
轮询技术的适用现状
现在仍使用轮询的场景大多是旧系统维护或实时性要求极低的场景,
- 内部管理后台的待办事项数量刷新。
- 非关键的监控图表数据,每5分钟更新一次。
- 兼容极老版本浏览器,不支持WebSocket的环境。
服务器主动推送消息到浏览器:SSE单向通道详解
如果你只需要服务器单方向给客户端发消息,那SSE是比轮询优雅得多的方案,它的全称是Server-Sent Events,翻译过来叫“服务器发送事件”。

SSE的核心原理和优势
SSE使用HTTP/1.1协议,通过Content-Type: text/event-stream 这个MIME类型建立一条持久化的HTTP连接,连接由客户端发起(使用EventSource API),之后服务器就能在这条连接上源源不断地推送文本数据。
- 使用场景:适合股票行情、实时日志推送、AI聊天中的流式内容输出。
- 自带断线重连:这是SSE最大的杀手锏,客户端连接断开后,浏览器会自动重新发起请求,无需开发者写额外的重连逻辑。
- 传输支持:不支持二进制数据,只能传UTF-8文本,如果需要传图片或文件,需要转成Base64格式,这会增加约33%的数据体积。
如何实现SSE消息推送:分步操作
以Node.js的Express框架为例,后端代码逻辑如下:
app.get('/events', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
const interval = setInterval(() => {
const data = { message: `当前时间:${new Date().toISOString()}` };
// 格式必须为 "data: "开头, nn结尾
res.write(`data: ${JSON.stringify(data)}nn`);
}, 1000);
req.on('close', () => clearInterval(interval));
});
前端接收代码也非常简短:
const source = new EventSource('/events');
source.onmessage = (event) => {
console.log('收到推送:', event.data);
document.getElementById('content').innerText = event.data;
};
SSE和WebSocket怎么选:从通信方向判断
经常有同学问SSE和WebSocket怎么选,这里的判断标准很简单:
| 对比维度 | SSE | WebSocket |
|---|---|---|
| 通信方向 | 单向(服务器 -> 客户端) | 双向(客户端 <-> 服务器) |
| 协议基础 | HTTP/1.1 | TCP升级后的独立协议 |
| 断线重连 | 浏览器原生支持 | 需手写心跳和重连逻辑 |
| 数据格式 | 仅文本 | 文本和二进制均可 |
| 复杂度 | 较低 | 较高 |
如果业务只是服务器单方面广播,选SSE更合适;如果涉及客户端发指令、协同编辑、在线游戏,就必须上WebSocket了。
网页实时推送消息怎么做:WebSocket全双工通信实战

WebSocket是当前实时通信的事实标准,它基于TCP协议,通过一次HTTP握手升级协议后,客户端和服务器就处于完全对等的地位,双方都能随时发送数据。
WebSocket的握手和消息帧
握手过程:客户端发送一个带有Upgrade: websocket头部的HTTP请求,服务器返回101 Switching Protocols状态码,连接即建立。
- 消息帧:消息帧包含FIN(标志最后一帧)、opcode(标识数据类型)和Payload(数据负载),这套机制让网络传输更省流量,因为它不像HTTP那样每次传输大量冗余头部。
- 性能表现:相比HTTP轮询,WebSocket的消息头开销从几百字节缩减到个位数(一般6字节左右),在需要高频收发消息的场景下,网络带宽占用能降低一个数量级。
如何搭建生产级的WebSocket服务
以Socket.IO(Node.js库)为例,生产环境部署步骤如下:
- 安装依赖:
npm install socket.io - 服务端初始化:
const http = require('http'); const { Server } = require('socket.io');
const server = http.createServer((req, res) => {
res.end('Server running');
});
const io = new Server(server, {
cors: { origin: "https://yourdomain.com" } // 务必限制来源
});
io.on('connection', (socket) => {
console.log('新客户端连接:', socket.id);
// 推送欢迎消息
socket.emit('welcome', { msg: '连接成功' });
// 监听客户端事件
socket.on('chat message', (data) => {
// 广播给所有客户端
io.emit('chat message', data);
});
});
server.listen(3000);
客户端连接:
```javascript
const socket = io('wss://yourdomain.com', { transports: ['websocket'] });
socket.on('connect', () => console.log('已连接'));
socket.on('welcome', (data) => console.log(data.msg));
处理实际业务中的心跳保活问题
长时间运行的WebSocket连接可能因网络问题假死,生产环境必须配置心跳检测机制。
- 服务端每30秒发送一个
ping帧。 - 若客户端在10秒内没有回复
pong,则判定连接断开,执行清理。
io.on('connection', (socket) => {
socket.isAlive = true;
socket.on('pong', () => socket.isAlive = true);
});
const interval = setInterval(() => {
io.clients().forEach((socket) => {
if (socket.isAlive === false) return socket.terminate();
socket.isAlive = false;
socket.ping();
});
}, 30000);
服务器如何推送消息的架构选型:穿透内网与即时通讯搭建实操

结合当下来看,很多开发者的真实需求不只是简单发消息,而是想在自己的应用里搭建一个完整的即时通讯(IM)模块,这时面临的问题是:如何穿透NAT和防火墙,让消息可靠送达。
公网服务器中转和P2P直连的取舍
- 公网中转模式:客户端都连接到一个公网上的转发服务器(如云服务器ECS),消息先发给服务器,再由服务器转发给目标用户,此方案实现简单、可靠性极高,需要购买一台有公网IP的云服务器,预算大约一年几百块钱(按轻量级配置)即可支撑几千名用户。
- P2P直连模式:通过WebRTC技术寻找端到端通道,服务器只负责信令协调,此方案节省服务器带宽,但NAT穿透成功率并非100%,且浏览器兼容性不一致,需要约20%的降级处理方案。
私有化部署场景:内网防火墙怎么处理
企业内网环境出于安全考虑,常用端口白名单和防火墙策略,部署WebSocket服务时,需要与网络管理员沟通开放特定端口。
- 例如默认WebSocket走
443端口(必须依赖HTTP/2协议扩展或TLS加密),如果走8080端口,则需确保防火墙规则允许TCP入站连接。 - 还有一点需要留意:在Nginx反代层配置WebSocket代理时,必须设置
proxy_set_header Upgrade $http_upgrade;以及proxy_set_header Connection "upgrade";,否则连接会直接断掉。
让服务器主动给你发消息:Q&A排查与深层解答
为什么我在家访问公司内网,网页收不到服务器推送的消息?
因为公司内网的服务器位于NAT设备之后,没有公网IP,公网客户端无法直接发起连接,解决办法是使用内网穿透工具(如Frp、Ngrok)建立隧道,或者在路由器上配置端口映射(Port Forwarding),将公网的指定端口映射到内网服务器的对应端口,完成设置后,外网的请求就能正确路由到你的WebSocket服务进程。
服务器向客户端发送HTTP消息和WebSocket消息在代码层面有什么区别?
HTTP消息是一次性响应,服务端使用res.write()后连接即关闭,属于无状态请求,WebSocket消息是长连接内的数据帧,服务端需先订阅connection事件拿到socket对象,后续使用socket.send()随时发送数据,连接生命周期内可以重复发送上万次消息,而无需重复握手,另一个细节是,HTTP响应头里没有Sec-WebSocket-Accept字段,而WebSocket握手成功后的响应必须带这个验证头。