什么是 WebSocket 服务器推送?
WebSocket 服务器推送指的是服务器可以主动向已经连接的客户端(通常是浏览器)发送数据,而无需客户端先发起请求。

这解决了传统 Web 通信模式的根本性瓶颈。
为什么需要 WebSocket 服务器推送?(对比传统模式)
为了理解 WebSocket 的优势,我们先看看它出现之前的通信方式。
传统模式:HTTP 轮询 vs 长轮询
| 特性 | HTTP 轮询 | HTTP 长轮询 | WebSocket |
|---|---|---|---|
| 通信模式 | 客户端主动、服务器被动 | 客户端主动、服务器被动 | 双向、全双工 |
| 工作原理 | 客户端定时(如每秒)向服务器发送请求,询问是否有新数据。 | 客户端发送请求后,服务器保持连接打开,直到有新数据可返回或超时,然后立即发送响应并关闭连接,客户端再立即发起下一次请求。 | 客户端和服务器之间建立一个持久化的连接,任何一方都可以随时向另一方发送消息。 |
| 实时性 | 差,数据有延迟(取决于轮询间隔),且延迟不固定。 | 较好,有新数据时能较快返回,但建立新连接本身也有开销。 | 极好,毫秒级延迟,数据可实时推送。 |
| 服务器资源 | 消耗大,大量无效的短连接频繁建立和销毁,占用大量服务器资源和带宽。 | 消耗较大,虽然长连接减少了建连次数,但大量客户端同时保持连接会占用服务器资源。 | 消耗小,连接建立后保持长连接,通信开销极小,服务器资源利用率高。 |
| 数据格式 | 纯文本(如 HTML, JSON) | 纯文本(如 HTML, JSON) | 帧,支持文本和二进制,效率更高。 |
| 场景 | 简单、不要求实时性的场景 | 实时性要求稍高,但无法使用 WebSocket 的场景(如极旧浏览器) | 聊天室、实时游戏、在线协作、金融行情、实时通知等高实时性场景。 |
WebSocket 通过一次握手建立一个持久的连接,彻底改变了服务器和客户端的通信关系,使得服务器能够像客户端一样主动“推送”信息,是实现真正实时通信的最佳选择。
WebSocket 如何工作?(握手与通信流程)
WebSocket 的通信分为两个阶段:握手 和 数据传输。

握手
握手过程是标准的 HTTP 请求/响应,目的是将 HTTP 升级为 WebSocket 协议。
-
客户端请求:浏览器向服务器发起一个特殊的 HTTP 请求,包含一个
Upgrade头部。GET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13 Sec-WebSocket-Protocol: chat, superchat
-
服务器响应:服务器收到请求后,如果支持 WebSocket,则返回一个
101 Switching Protocols状态码的响应,表示协议切换成功。HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
一旦握手完成,HTTP 连接就变成了一个 WebSocket 连接,后续的数据传输就不再遵循 HTTP 协议,而是遵循 WebSocket 自己定义的帧格式。

数据传输
连接建立后,服务器和客户端就可以自由地发送消息了,数据被分割成小的“帧”进行传输,这使得数据可以一边发送一边接收,并且可以处理大量数据而不会阻塞。
如何实现 WebSocket 服务器推送?(代码示例)
下面我们用几种主流的技术栈来展示如何实现一个简单的“服务器推送当前时间”的例子。
示例场景
客户端连接到服务器,服务器每秒向客户端推送一次当前的系统时间。
Node.js (使用 ws 库)
ws 是 Node.js 中最流行、性能最好的 WebSocket 库之一。
服务器端代码 (server.js)
// 1. 安装 ws: npm install ws
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 }); // 创建 WebSocket 服务器,监听 8080 端口
console.log('WebSocket server is running on ws://localhost:8080');
wss.on('connection', (ws) => {
// 当有新的客户端连接时触发
console.log('New client connected');
// 2. 服务器主动推送数据
const intervalId = setInterval(() => {
const now = new Date().toLocaleTimeString();
// 使用 ws.send() 方法向客户端发送消息
ws.send(`Server time: ${now}`);
}, 1000); // 每秒推送一次
// 当客户端断开连接时触发
ws.on('close', () => {
console.log('Client disconnected');
clearInterval(intervalId); // 清除定时器,避免内存泄漏
});
// 当收到客户端消息时触发
ws.on('message', (message) => {
console.log(`Received from client: ${message}`);
});
});
客户端代码 (index.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">WebSocket Client</title>
</head>
<body>
<h1>WebSocket Time Push</h1>
<div id="output"></div>
<script>
// 3. 客户端连接服务器
const socket = new WebSocket('ws://localhost:8080');
// 当连接成功时触发
socket.onopen = function(event) {
console.log('Connected to WebSocket server');
document.getElementById('output').innerHTML += '<p>Connected!</p>';
};
// 当收到服务器消息时触发
socket.onmessage = function(event) {
console.log('Message from server:', event.data);
// 将收到的时间显示在页面上
document.getElementById('output').innerHTML += `<p>${event.data}</p>`;
};
// 当连接关闭时触发
socket.onclose = function(event) {
console.log('Disconnected from WebSocket server');
document.getElementById('output').innerHTML += '<p>Disconnected.</p>';
};
// 当发生错误时触发
socket.onerror = function(error) {
console.error('WebSocket Error:', error);
};
</script>
</body>
</html>
运行方式:
- 保存
server.js并运行node server.js。 - 将
index.html放在任何一个 Web 服务器(如 Nginx, Apache 或简单的python -m http.server)中,然后用浏览器打开它。 - 你将看到页面上每秒更新一次从服务器推送来的时间。
Go (使用 gorilla/websocket 库)
Go 语言以其高并发性能而闻名,非常适合构建 WebSocket 服务器。
服务器端代码 (main.go)
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/gorilla/websocket" // 需要先安装: go get github.com/gorilla/websocket
)
// 定义一个 upgrader,用于将 HTTP 连接升级为 WebSocket 连接
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // 允许所有来源的连接,生产环境应更严格
},
}
// 处理 WebSocket 连接的函数
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
// 升级 HTTP 连接
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("Upgrade error:", err)
return
}
defer conn.Close() // 确保连接关闭
log.Println("New client connected")
// 使用一个无限循环来持续向客户端推送数据
for {
// 获取当前时间
currentTime := time.Now().Format("15:04:05")
message := fmt.Sprintf("Server time: %s", currentTime)
// 向客户端发送消息
err := conn.WriteMessage(websocket.TextMessage, []byte(message))
if err != nil {
log.Println("Write error:", err)
break // 如果发送失败,断开循环
}
// 等待一秒
time.Sleep(1 * time.Second)
}
}
func main() {
// 设置 WebSocket 路由
http.HandleFunc("/ws", handleWebSocket)
// 启动 HTTP 服务器
port := "8080"
log.Printf("WebSocket server starting on :%s...", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
客户端代码与 Node.js 的示例完全相同。
WebSocket 的应用场景
几乎所有需要低延迟、高频率数据交换的场景都适合使用 WebSocket。
- 即时通讯:微信、QQ、Slack 等聊天应用。
- 实时协作:Google Docs、腾讯文档等多人在线编辑。
- 在线游戏:服务器需要实时将玩家位置、状态等信息广播给所有玩家。
- 金融数据:股票行情、外汇牌价的实时推送。
- 实时通知:社交网络的新消息提醒、系统状态的实时更新。
- 物联网:设备状态的实时监控和控制。
挑战与注意事项
- 兼容性:现代浏览器(Chrome, Firefox, Safari, Edge)对 WebSocket 的支持非常好,但如果需要支持非常旧的浏览器(如 IE10),需要做好兼容性检查。
- 服务器资源:虽然单个连接开销小,但成千上万的并发长连接会占用大量内存和文件描述符,需要合理配置服务器和进行负载均衡。
- 心跳机制:为了检测连接是否还活着,防止网络设备(如 NAT 路由器)因长时间无数据传输而切断连接,通常需要实现一个“心跳”机制(客户端和服务器定期发送简短的 Ping/Pong 消息)。
- 安全性:
- 使用
wss://:务必使用 WebSocket Secure (wss://),它通过 TLS/SSL 加密,防止数据被窃听。 - 认证:在握手阶段或连接建立后,需要一套机制来验证客户端的身份(如 JWT Token, Cookie)。
- 使用
- 可扩展性:当单台服务器无法承载所有连接时,需要考虑使用专门的 WebSocket 集群方案,
- Nginx 作为反向代理和负载均衡器。
- SocketCluster、Socket.IO(虽然 Socket.IO 主要是为了兼容性,但也支持集群模式)等专门为高并发设计的框架。
- 使用 Redis Pub/Sub 或 MQTT 等消息队列在多个 WebSocket 服务器实例间同步消息。
WebSocket 服务器推送是构建现代实时 Web 应用的基石,它通过建立一个持久的全双工连接,彻底改变了服务器与客户端的交互方式,实现了高效、低延迟的数据推送,掌握其原理和实现方式,对于开发高性能的实时应用至关重要。
