Yang's blog Yang's blog
首页
Java
密码学
机器学习
命令手册
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

xiaoyang

编程爱好者
首页
Java
密码学
机器学习
命令手册
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • SpringCloud

    • 微服务架构介绍
    • SpringCloud介绍
    • Spring Cloud:生产者与消费者
    • Spring Cloud Eureka:构建可靠的服务注册与发现
    • Spring Cloud Ribbon:负载均衡
    • Spring Cloud Fegin:服务调用
    • Spring Cloud Hystrix:熔断器
    • Spring Cloud Zuul:统一网关路由
    • Spring Cloud Config:配置中心
  • Java后端框架

    • LangChain4j

      • 介绍
      • 快速开始
      • Chat and Language Models
      • Chat Memory
      • Model Parameters
      • Response Streaming
      • AI Services
      • Agent
      • Tools (Function Calling)
      • RAG
      • Structured Outputs
      • Classification
      • Embedding (Vector) Stores
      • Image Models
      • Quarkus Integration
      • Spring Boot Integration
      • Kotlin Support
      • Logging
      • Observability
      • Testing and Evaluation
      • Model Context Protocol
  • 八股文

    • 操作系统
    • JVM介绍
    • Java多线程
    • Java集合框架
    • Java反射
    • JavaIO
    • Mybatis介绍
    • Spring介绍
    • SpringBoot介绍
    • Mysql
    • Redis
    • 数据结构
    • 云计算
    • 设计模式
    • 计算机网络
    • 锁核心类AQS
    • Nginx
  • 前端技术

    • 初识Vue3
    • Vue3数据双向绑定
    • Vue3生命周期
    • Vue-Router 组件
    • Pinia 集中式状态存储
  • 中间件

    • RocketMQ
  • 开发知识

    • 请求参数注解
    • 时间复杂度和空间复杂度
    • JSON序列化与反序列化
    • Timestamp vs Datetime
    • Java开发中必备能力单元测试
    • 正向代理和反向代理
    • 什么是VPN
    • 正则表达式
  • Java
  • 开发知识
xiaoyang
2025-03-11
目录

后端服务端主动推送消息的常见方式

# Web 后端服务端主动推送消息的常见方式

# 1. 引言

在传统的 Web 通信模式中,客户端(如浏览器、移动应用)需要向服务器发起请求,服务器才能返回数据。这种模式通常被称为 请求-响应(Request-Response) 模型,即:

  1. 客户端请求数据:用户操作(如打开网页、点击按钮)会触发 HTTP 请求,发送到服务器。
  2. 服务器处理请求:服务器接收请求后,查询数据库、执行逻辑运算,并返回响应数据。
  3. 客户端接收并渲染数据:浏览器或应用解析服务器返回的数据,并更新界面。

这种模式虽然简单直观,但它有一个核心问题:服务器无法主动通知客户端新数据的到来。如果客户端想要获取最新数据,就必须主动发送请求,这在某些实时性要求较高的场景下并不高效例如:

  • 即时通讯(IM):如微信、Slack、WhatsApp 等应用,需要服务器主动推送新消息,否则用户需要频繁刷新才能看到新消息。
  • 股票、加密货币行情:金融市场的价格波动快,客户端需要尽可能快地收到最新报价。
  • 系统通知:如社交媒体的消息提醒(微博、推特)、邮件系统的新邮件通知等。
  • 多人协作应用:如在线文档协作(Google Docs)、多人游戏,服务器需要向所有客户端推送最新状态更新。
  • 直播、弹幕系统:视频直播、弹幕等场景下,服务器需要向成千上万的观众推送最新内容。

由于传统 HTTP 请求模式无法满足这些需求,因此出现了一系列优化方案,如短轮询、长轮询、WebSocket、Server-Sent Events(SSE)、MQTT 等,这些技术允许服务器主动向客户端推送消息,减少延迟并提升用户体验。

# 2. 轮询(Polling)

轮询(Polling)是一种早期解决服务器推送需求的方式,它的核心思想是:客户端不断向服务器发送请求,检查是否有新数据可用。

轮询方法的主要优点是实现简单,可以直接使用标准的 HTTP 请求来完成。但是,它也有较大的缺点,例如 资源消耗高、延迟不可控。

image-20250311210802577

# 3.1 短轮询(Short Polling)

工作原理

短轮询的实现方式如下:

  1. 客户端定期(如每 5 秒)向服务器发送 HTTP 请求,询问是否有新数据。
  2. 服务器检查是否有新数据,并返回相应的响应:
    • 有新数据:返回最新的数据。
    • 没有新数据:返回空响应(或重复返回旧数据)。
  3. 客户端收到响应后,等待一段时间后再次发送请求。

示例代码(JavaScript Fetch API)

function shortPolling() {
    fetch('/api/messages')
        .then(response => response.json())
        .then(data => console.log('Received data:', data))
        .catch(error => console.error('Error:', error));

    setTimeout(shortPolling, 5000); // 每 5 秒轮询一次
}
shortPolling();
1
2
3
4
5
6
7
8
9

优缺点分析

✅ 优点:

  • 实现简单,直接基于 HTTP 请求,无需额外协议支持。
  • 兼容性好,适用于几乎所有浏览器和后端框架。

❌ 缺点:

  • 资源浪费:即使没有新数据,客户端仍然会不断发起请求,占用带宽和服务器资源。
  • 延迟不可控:如果轮询间隔太长,可能会错过实时更新;如果间隔太短,会增加服务器负载。
  • 不适用于高并发场景:大规模用户同时轮询可能会导致服务器崩溃。

# 3.2 长轮询(Long Polling)

短轮询的问题在于 即使没有新数据,客户端也会不断请求服务器,导致大量无效请求。长轮询(Long Polling)是一种改进方案,它的核心思路是:让服务器保持连接,直到有新数据时再返回响应。

工作原理

  1. 客户端发送 HTTP 请求给服务器,询问是否有新数据。

  2. 服务器不立即返回响应,而是保持连接,直到有新数据可用或超时:

    • 有新数据:服务器立即返回数据,客户端收到数据后再次发起新请求。
  • 没有新数据:服务器保持连接,直到超时后返回空响应,客户端再发起新请求。

示例代码(JavaScript Fetch API + Express 后端)

前端:

function longPolling() {
    fetch('/api/messages')
        .then(response => response.json())
        .then(data => {
            console.log('Received data:', data);
            longPolling(); // 继续下一次请求
        })
        .catch(error => {
            console.error('Error:', error);
            setTimeout(longPolling, 5000); // 发生错误时稍等后重试
        });
}

longPolling();
1
2
3
4
5
6
7
8
9
10
11
12
13
14

后端(Node.js + Express):

const express = require('express');
const app = express();

app.get('/api/messages', (req, res) => {
    // 模拟新消息
    setTimeout(() => {
        res.json({ message: "New message from server!" });
    }, Math.random() * 10000); // 服务器等待 0~10 秒后返回数据
});

app.listen(3000, () => console.log('Server running on port 3000'));
1
2
3
4
5
6
7
8
9
10
11

优缺点分析

✅ 优点:

  • 减少无效请求,只有在有新数据时才返回,避免了短轮询的资源浪费。
  • 相对较低的服务器负载,减少了 HTTP 请求的次数。

❌ 缺点:

  • 仍然有一定的延迟,因为每次请求仍然是独立的,数据更新的时间受请求间隔影响。
  • 服务器需要维持连接,如果并发量大,服务器需要处理大量长时间挂起的请求,可能导致连接池耗尽。
  • 不适用于 Web 浏览器以外的长连接场景,如物联网设备的低功耗通信。

# 3.3 轮询方式的适用场景

方案 适用场景 优点 缺点
短轮询 低访问量的应用,如定期检查新邮件 简单易实现,兼容性好 资源消耗大,延迟不可控
长轮询 实时性要求较高但用户数量有限的应用,如客服聊天 减少无效请求,提高实时性 服务器需维持大量挂起请求,影响性能

随着应用对实时性的要求越来越高,轮询方式逐渐暴露出性能问题。因此,开发者开始寻找更高效的解决方案,如 WebSocket、Server-Sent Events(SSE)等,这些技术可以提供更低延迟、低资源消耗的双向通信能力,我们将在接下来的章节中详细介绍。

# 3. WebSocket:全双工通信

# 3.1 WebSocket 介绍

WebSocket 是一种 全双工(Full-Duplex)通信协议,允许客户端与服务器之间 建立持久连接,并在连接存活期间实现 实时的双向数据传输。

在传统 HTTP 轮询方案中,客户端需要不断发送请求获取新数据,而 WebSocket 通过 一次握手,建立持久连接,之后客户端和服务器可以随时主动发送消息,无需额外的请求开销,从而极大提高了通信效率。

image-20250311205836465

WebSocket 工作流程

  1. HTTP Upgrade 进行协议升级
    • WebSocket 连接的建立始于标准的 HTTP 请求,客户端发送 Upgrade 头 请求服务器升级协议:
    • 服务器同意后,连接切换为 WebSocket,后续通信不再依赖 HTTP。
  2. 建立连接后,客户端和服务器可双向通信
    • 连接建立后,服务器和客户端可以随时主动发送和接收消息,不再需要客户端轮询服务器。
  3. 连接保持,支持低延迟通信
    • WebSocket 连接在 不关闭的情况下会一直保持,只有当客户端或服务器主动关闭,或者遇到网络问题时才会断开。

示例代码

前端 WebSocket 客户端(JavaScript):

const socket = new WebSocket('ws://localhost:8080');

// 监听连接打开
socket.onopen = function () {
    console.log('WebSocket 连接已建立');
    socket.send('Hello Server!'); // 发送消息给服务器
};

// 监听服务器消息
socket.onmessage = function (event) {
    console.log('收到服务器消息:', event.data);
};

// 监听连接关闭
socket.onclose = function () {
    console.log('WebSocket 连接已关闭');
};

// 监听错误
socket.onerror = function (error) {
    console.log('WebSocket 发生错误:', error);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

后端 WebSocket 服务器(Node.js + ws 库):

const WebSocket = require('ws');

const server = new WebSocket.Server({ port: 8080 });

server.on('connection', (socket) => {
    console.log('客户端连接成功');

    socket.on('message', (message) => {
        console.log('收到客户端消息:', message);
        socket.send('服务器响应: ' + message); // 服务器主动推送消息
    });

    socket.on('close', () => {
        console.log('客户端断开连接');
    });
});

console.log('WebSocket 服务器运行在 ws://localhost:8080');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 3.2 适用场景

WebSocket 适用于 高实时性、低延迟、双向通信需求的场景,例如:

✅ 实时聊天应用

  • 微信、WhatsApp、Slack 等即时通讯应用需要服务器在新消息到达时立即推送给客户端。

✅ 多人协作编辑

  • Google Docs、在线白板等多人协作应用,需要所有用户共享编辑状态。

✅ 在线游戏

  • 需要低延迟同步玩家状态(如 FPS、多人 RPG)。

✅ 实时监控

  • 物联网(IoT)设备、服务器日志监控等需要实时数据推送。

✅ 金融交易

  • 交易所行情、股票价格变动等,WebSocket 可快速推送最新价格。

# 3.3 WebSocket 存在的问题

尽管 WebSocket 提供了高效的实时通信,但它仍然存在一些挑战和需要注意的地方。

1️⃣ 连接数管理

  • WebSocket 连接会一直保持,因此服务器需要管理大量的并发连接,特别是在高并发场景下,如 WebSocket 直播。
  • 服务器通常需要 连接池、负载均衡 来管理 WebSocket 连接,例如使用 Nginx + WebSocket 代理。

2️⃣ 网络状态变化处理

  • 如果用户的网络断开(如手机切换 Wi-Fi 到 4G),WebSocket

# 4. Server-Sent Events(SSE)

# 4.1 SSE 介绍

Server-Sent Events(SSE) 是一种基于 HTTP 协议的单向推送技术,允许 服务器主动向客户端推送数据。
与 WebSocket 不同,SSE 仅支持服务器向客户端发送数据,但它比 WebSocket 更加轻量级,并且直接支持 浏览器的 EventSource API。

比如当你在使用 ChatGPT 时,当你询问它问题时,你会看到它会逐字地将回答显示出来,实际上这是 ChatGPT 将先计算出的数据主动的“推送”给你,采用 SSE 技术边计算边返回,避免接口等待时间过长而直接关闭页面。

image-20250311204234864

# 4.2 SSE连接过程

它的连接过程主要包括以下步骤:

  1. 客户端发起请求
    客户端(通常是浏览器)通过 JavaScript 使用 EventSource API 向服务器的 SSE 端点发送一个 HTTP GET 请求。
    示例代码:

    const source = new EventSource('/events');
    
    1

    这里 /events 是服务器提供的事件流端点的 URL。

  2. 设置请求头
    客户端会自动在请求中设置特定的头信息,以表明它期望接收 SSE 数据流:

    • Accept: text/event-stream:告诉服务器客户端期望接收事件流。
    • Connection: keep-alive:指示保持长连接。
    • Cache-Control: no-cache:避免缓存干扰。
  3. 服务器响应并建立连接
    服务器接收到请求后,返回一个 HTTP 响应,状态码通常为 200 OK,并设置响应头:

    • Content-Type: text/event-stream:表明返回的是 SSE 事件流。
    • Connection: keep-alive:保持连接开放。
    • Transfer-Encoding: chunked(可选):支持分块传输。
      连接建立后,服务器不会立即关闭,而是保持开放状态,随时推送数据。
  4. 服务器推送事件
    服务器通过已建立的连接向客户端发送数据,数据格式遵循 SSE 协议:

    • 数据以纯文本(UTF-8 编码)发送。
    • 每个事件由若干字段组成(如 event、data、id 等),以换行符分隔,事件之间用空行(两个换行符 \n\n)分隔。
      示例事件:
    event: message
    data: 这是一条实时消息
    id: 1
    
    
    1
    2
    3
    4

    客户端通过 EventSource 的监听器接收这些事件,例如:

    source.onmessage = function(event) {
        console.log(event.data); // 输出:这是一条实时消息
    };
    
    1
    2
    3
  5. 连接保持与重连机制

    • 如果连接意外断开(例如网络中断),EventSource 会自动尝试重连,默认间隔通常为 2-3 秒(可通过服务器发送 retry: 毫秒数 自定义)。
    • 客户端会携带上一次接收到的 Last-Event-ID(如果有)以恢复数据流。
  6. 关闭连接

    • 客户端可通过调用 source.close() 主动关闭连接。
    • 服务器也可通过关闭响应流或返回非 text/event-stream 的内容类型来终止连接。

注意事项

  • 单向通信:SSE 是服务器到客户端的单向推送,客户端无法通过同一连接向服务器发送数据。
  • 浏览器限制:在 HTTP/1.1 下,每个域名最多支持 6 个并发 SSE 连接;HTTP/2 可协商更多连接。
  • 数据格式:仅支持 UTF-8 文本,不支持二进制数据。

# 4.3 Java后端实现方式

# 4.3.1 SseEmitter(Spring MVC)

  • SseEmitter 属于 Spring MVC,适用于 Servlet 线程模型(同步、阻塞)。
  • 通过 SseEmitter.send() 发送事件,由 Servlet 线程池 维护连接。
  • 适用于 非响应式(Blocking) 应用程序,希望在传统的 Spring MVC 方式下实现 SSE。
  • 底层依赖:基于 Servlet API,适合 Tomcat、Jetty 等 Servlet 容器。

示例:

@RestController
public class SseController {

    @GetMapping("/sse")
    public SseEmitter stream() {
        SseEmitter emitter = new SseEmitter();
        Executors.newSingleThreadExecutor().execute(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    emitter.send("Message " + i);
                    Thread.sleep(1000);
                }
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });
        return emitter;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

特点:

  • SseEmitter 适合 同步(阻塞)模型,由 Spring MVC 线程池管理。
  • 不适用于高并发,因为每个连接都会占用一个服务器线程。

# 4.3.2 Flux(Project Reactor - WebFlux)

  • Flux 是 Spring WebFlux 响应式编程的一部分,基于 Reactive Streams 规范,适用于 异步非阻塞 处理。
  • 通过 Flux.interval() 或 Flux.generate() 创建事件流,由 Reactor 线程模型 处理。
  • 适用于 高并发、低延迟 的 SSE 应用场景。
  • 底层依赖:基于 Netty、Undertow 等异步 Web 服务器,不依赖 Servlet API。

示例:

@RestController
public class ReactiveSseController {

    @GetMapping(value = "/flux", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> streamFlux() {
        return Flux.interval(Duration.ofSeconds(1))
                   .map(i -> "Message " + i)
                   .take(5);
    }
}
1
2
3
4
5
6
7
8
9
10

特点:

  • Flux 采用 异步(非阻塞) 模型,更适合 WebFlux(Netty)。
  • 适用于 高吞吐、并发量大 的 SSE 应用场景。
  • 响应式流在 WebFlux 运行时进行优化,不占用固定线程资源。

# 4.3.3 核心区别

特性 SseEmitter (Spring MVC) Flux (Spring WebFlux)
运行模式 阻塞(Blocking) 非阻塞(Non-Blocking)
编程模型 基于 Servlet 线程池 Reactive Streams 响应式
底层容器 Servlet(Tomcat、Jetty) Netty(默认)、Undertow
并发能力 低(一个连接占用一个线程) 高(Reactor 线程池管理)
适用场景 传统 Spring MVC + SSE 高吞吐 SSE,Reactive 应用
线程管理 服务器线程池维护连接 Reactor 事件驱动
编辑 (opens new window)
上次更新: 2025/04/01, 01:48:12
最近更新
01
操作系统
03-18
02
Nginx
03-17
03
锁核心类AQS
03-11
更多文章>
Theme by Vdoing | Copyright © 2023-2025 xiaoyang | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式