实时通信
HTTP
HTTPS 是 HTTP 的安全版本,在 HTTP 和 TCP 之间加入了 SSL/TLS 加密层,保证:
HTTPS作为一个加密层,可以与所有版本的HTTP结合使用
加密:防止数据被窃听。
完整性:防止数据被篡改。
身份验证:验证服务器身份(通过证书)。
在生产环境中,务必使用 HTTPS 保护用户数据(如密码、Token)
协议是:浏览器 ↔ 服务器 每次建立连接时「自动协商」出来的,它是灵活、动态、可同时支持多种的
HTTP协议版本并不是一个网站(域名)固定不变的,而是动态协商、灵活多变的。它既不是绑定在域名上,也不是根据具体的接口(URL路径)来定,而是由客户端(浏览器)和服务器在连接建立时共同协商决定的。同一个域名下的不同请求,甚至在不同时间、不同网络环境下,完全可能使用不同的HTTP版本。HTTP协议的设计目标之一是向后兼容和平滑升级
HTTP版本进阶
HTTP/1.0:每个请求/响应后关闭连接。
HTTP/1.1:引入持久连接(Keep-Alive)、管道化(Pipelining)、Host 头等
HTTP/2:二进制分帧、多路复用、头部压缩,大幅提升性能。(基于TLS的ALPN)
HTTP/3:基于 QUIC(UDP),减少连接延迟,提高弱网环境表现(基于Alt-Svc)
在Network面板里随便点开一个请求,看看它的 Alt-Svc 头。如果看到了,就说明这个网站已经为你准备好了更快的HTTP/3连接
HTTP 协议不是绑定域名,也不是绑定接口
是浏览器和服务器「每次连接自动协商」的
服务器可以同时开多种协议,自动兼容
对前端开发者来说:基本不用管,自动适配
HTTP 轮询
信道(Channel):在网络通信中,通常指一个TCP连接。每个TCP连接都会占用服务器端的文件描述符、内存等资源。并发连接数过高可能导致服务器资源耗尽。
HTTP 轮询是一种基于传统 HTTP 请求-响应模式的伪实时通信技术
短轮询(夺命连环call):客户端按照一定的时间间隔,反复向服务器发送 HTTP 请求,询问是否有新数据,服务器收到请求后立即返回响应。
长轮询:服务器接收到请求后,如果没有新数据,会保持连接挂起,直到有新数据或超时才返回,这可以减少无效响应。
实时性低,长轮训较高一点;资源消耗短轮询较高,长轮训较低
短轮询虽然对并发连接数友好,但它会带来巨大的请求频率,适合客户端数量不大的场景
长轮询的每个请求都会长期占用一个连接,不利于高并发
例子:网页扫描二维码
SSE
Server-Sent Events 是一种允许服务器主动向浏览器推送事件的技术。它基于纯 HTTP 协议,客户端通过 EventSource API 建立连接,服务器保持连接打开,并以特定格式 (text/event-stream) 发送数据。SSE 具有以下特点:
- 单向通信:仅服务器可推送数据,客户端不能通过该连接发送数据(适合通知类场景)。
- 自动重连:连接断开后浏览器会自动尝试重新连接。
- 轻量简单:无需 WebSocket 的复杂握手和协议升级。
例子:餐厅等位叫号、股票报价、新闻推送
- 注意:SSE 的限制: 浏览器对同一个域名的 SSE 连接数通常有限制(Chrome 默认 6 个),如果开了太多标签页可能会失效
应用:社区在线人数统计功能
目标:实时向所有在线用户显示当前社区在线人数。
实现步骤
- 客户端发起 SSE 连接
前端通过 new EventSource(‘/api/community/events?token=…’) 建立连接,并在 URL 中携带 JWT token 用于身份验证。 - 服务端接收连接并验证
接口 streamCommunityEvents 解析 token,验证用户身份。
设置 SSE 响应头,保持连接打开。
生成唯一 clientId,将客户端信息(id、userId、response 对象)存入全局 clients Map 中(调用 addCommunityClient)。 - 广播当前在线人数
addCommunityClient 内部调用 broadcastOnlineCount。
broadcastOnlineCount 遍历 clients Map,向每个客户端发送 event: online 事件,数据为 { count: clients.size }。 - 维护心跳
服务端每隔 20 秒向每个客户端发送注释行 :keep-alive\n\n,防止代理或浏览器因长时间无数据而超时关闭连接。
客户端断开处理
当客户端关闭页面或网络中断时,触发 res 对象的 close 事件。
在 close 回调中清理心跳定时器,并调用 removeCommunityClient(clientId) 从 clients Map 中移除该客户端。
removeCommunityClient 再次触发 broadcastOnlineCount,更新所有客户端的在线人数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19graph TD
A[客户端打开社区页面] --> B[创建 EventSource 连接]
B --> C[服务端接收 /events 请求]
C --> D{验证 token}
D -- 无效 --> E[返回 401]
D -- 有效 --> F[设置 SSE 响应头]
F --> G[生成 clientId, 存入 clients Map]
G --> H[调用 broadcastOnlineCount]
H --> I[遍历 clients, 发送 online 事件]
I --> J[启动心跳定时器]
J --> K[客户端接收 online 事件, 更新在线人数]
K --> L{客户端断开?}
L -- 是 --> M[触发 close 事件]
M --> N[清除心跳, 调用 removeCommunityClient]
N --> O[从 clients 删除该 client]
O --> P[再次 broadcastOnlineCount]
P --> Q[其他客户端收到更新后在线人数减1]
L -- 否 --> K
应用:新帖实时推送功能
目标:当有新帖子发布并审核通过后,立即向所有在线用户推送通知。
实现步骤
- 用户发布帖子
前端提交发帖表单,调用 createCommunityPost 接口。
服务端进行内容验证、敏感词检查、权限判断,若通过且无需审核(status = 1),则帖子立即生效。 - 触发推送
在帖子创建成功后,如果 status === 1 且 publish_status === 1,则调用 broadcastNewPost 函数。
broadcastNewPost 遍历当前所有活跃客户端(clients Map),向每个客户端写入 event: new_post 事件,数据包含帖子 ID、标题、作者等关键信息。 - 客户端接收并展示
前端 EventSource 监听 new_post 事件,提取数据后可在页面右上角或列表中动态显示“有新帖”的提示,或直接插入新帖卡片。1
2
3
4
5
6
7
8
9
10graph TD
A[用户提交发帖] --> B[服务端 createCommunityPost]
B --> C{验证审核是否通过}
C -- 否 --> D[返回结果, 不推送]
C -- 是 --> E[帖子保存成功, status=1]
E --> F[调用 broadcastNewPost]
F --> G[遍历 clients Map]
G --> H[对每个客户端发送 new_post 事件]
H --> I[客户端监听 new_post 事件]
I --> J[前端更新 UI, 显示新帖通知]
关键技术点总结
技术点 说明
SSE 连接管理 使用 Map 存储所有活跃连接,key 为唯一 ID,value 为包含 res 对象的 client 信息。
广播机制 遍历 Map,通过每个 client 的 res.write() 发送格式化消息。
心跳维持 定时发送注释行,防止连接因空闲被关闭。
连接清理 监听 res.on(‘close’) 事件,及时移除断开的客户端,并更新在线人数。
事件格式 event: 事件名\n + data: JSON字符串\n\n,符合 SSE 规范。
身份验证 连接 URL 中携带 JWT token,服务端验证后建立连接,确保安全性。
心跳检测
心跳检测是一种机制,定期在通信双方(客户端和服务器)之间发送一个小数据包(心跳包),用来确认对方是否还“活着”(即连接是否正常)。如果一方在指定时间内没有收到心跳响应,就认为连接已经断开,从而主动关闭连接并尝试重连。
比喻:两个人打电话,如果长时间不说话,其中一人可能会说“喂,你还在吗?”对方回应“在呢”。如果没回应,就知道电话可能断了。
- 心跳间隔设置注意事项
间隔不宜过短:太频繁会增加网络和服务器负担。一般 3060 秒是比较合理的范围。10 秒,考虑到网络延迟,避免误判。
超时时间应略大于间隔:例如心跳间隔 30 秒,超时时间可设为 5
考虑移动网络:在移动端,网络切换或休眠可能导致心跳中断,可以适当缩短间隔,但也要注意省电。
SSE需要心跳检测(保持连接活跃,目的是保持连接不被网络中间件关闭)
SSE 需要心跳,但目的是保持连接不被网络中间件关闭,而不是检测对方是否存活(因为 SSE 是单向的,服务器不会关心客户端是否还活着)
SSE 是基于 HTTP 的长连接,服务器向客户端持续发送数据。但网络中间件(如 Nginx、路由器、防火墙)可能会因为连接长时间空闲而自动断开。例如,Nginx 默认 proxy_read_timeout 为 60 秒,如果 60 秒内没有数据传输,它会关闭连接。这样客户端就收不到后续消息了。
因此,我们需要定期发送“心跳”来告诉中间件“连接还在使用”,防止超时断开
SSE 的心跳如何实现
SSE 协议本身没有规定心跳机制,实现方法简单:服务器定期发送注释行(:\n\n)
相比 WebSocket 的双向心跳,SSE 的心跳是单向的(仅服务器→客户端),且不需要客户端响应
服务器可以定期发送注释行(以冒号 : 开头)作为心跳。
浏览器收到注释行会直接忽略,不会触发 onmessage 事件,但连接上的数据流动足以重置网络设备的超时计时器。
WebSocket 需要心跳检测
WebSocket 连接虽然是长连接,但网络环境复杂,可能会遇到以下问题:
中间设备超时断开:网络中的路由器、防火墙、代理服务器等可能会自动关闭长时间没有数据传输的连接(例如 Nginx 默认 60 秒无数据就断开)。
客户端或服务器异常崩溃:如果一方突然崩溃,另一方可能无法立刻感知,导致连接变成“僵尸连接”,占用资源。
网络闪断:短暂的网络故障可能导致连接中断,但双方没有立即感知。
保持连接活跃:有些移动网络或浏览器对空闲连接有超时限制,定期发送心跳可以“续命”。
心跳检测就是为了解决这些问题,让双方能够及时知道连接状态,并在断开后迅速恢复。
WebSocket 协议本身提供了两种心跳相关的机制:
协议级 Ping/Pong 帧(推荐)
WebSocket 协议定义了两种控制帧:Ping 和 Pong。
一方可以发送 Ping 帧,对方收到后必须自动回复 Pong 帧(协议规定)。
这些帧非常轻量,不包含应用数据,只用于连接健康检查。
浏览器原生 WebSocket API 没有暴露发送 Ping 帧的方法(出于安全考虑),但服务器可以主动发送 Ping,浏览器会自动回复 Pong。前端可以通过监听 onmessage 来接收自定义的 Pong,但无法直接操作 Ping/Pong 帧。应用层心跳(自定义消息)
如果无法使用协议级 Ping/Pong(例如前端需要主动检测),可以在应用层实现心跳:
客户端定期发送一个特定格式的消息(如 { type: ‘heartbeat’ })。
服务器收到后回复一个心跳响应(如 { type: ‘heartbeat_ack’ })。
如果客户端在超时时间内未收到响应,则认为连接断开。
这种方式更灵活,但需要自己处理消息解析和超时逻辑。
WebSocket
原理:通过一次 HTTP 握手(Upgrade 头),将协议从 HTTP 切换为 WebSocket 协议,之后客户端和服务器之间可以双向自由传输数据(文本或二进制)
握手过程(理解即可):
客户端发 GET 请求,带上 Upgrade: websocket、Connection: Upgrade 以及 Sec-WebSocket-Key。
服务器返回 101 Switching Protocols,同时返回 Sec-WebSocket-Accept。
之后协议切换为 WebSocket,通信不再走 HTTP。
特点:
全双工,双向实时通信。
支持文本和二进制数据。
无同源策略限制
例子:实时聊天室、在线多人游戏、协同编辑(如飞书文档)



