SSE服务器推送事件原理深度解析与实战应用

一、什么是SSE

SSE(Server-Sent Events) 是一种基于HTTP的服务器推送技术,允许服务器向客户端单向发送事件流。它是HTML5规范的一部分,专为服务器到客户端的单向实时通信而设计。

1.1 SSE的核心特性

  • 单向通信:仅支持服务器向客户端推送数据
  • 基于HTTP:使用标准HTTP协议,无需额外的连接建立
  • 自动重连:浏览器会自动处理断线重连
  • 文本格式:传输的数据必须是文本格式
  • 事件模型:支持事件类型和ID,便于客户端处理

二、SSE工作原理

2.1 连接建立流程

服务器 客户端 服务器 客户端 保持连接开放 持续传输... 发送HTTP请求Accept: text/event-stream 返回200 OKContent-Type: text/event-stream data: 消息1\n\n data: 消息2\n\n data: 消息3\n\n 连接断开 自动重连请求Last-Event-ID: xxx 继续发送后续消息

2.2 SSE数据格式

SSE的传输格式非常简单,每条消息由一个或多个字段组成,以换行符分隔:

复制代码
字段名: 值\n

常用字段:

  • data: 消息内容(必需)
  • event: 事件类型(可选)
  • id: 事件ID(可选)
  • retry: 重连间隔时间(可选,单位毫秒)

示例:

复制代码
id: 1
event: message
data: {"content": "Hello SSE"}

id: 2
event: notification
data: {"title": "新消息", "count": 5}

data: 简单文本消息

注意: 每条消息必须以两个换行符 \n\n 结束。

2.3 消息类型处理





SSE消息到达
有event字段?
触发对应事件监听器
触发默认message事件
客户端处理特定事件
有id字段?
保存Last-Event-ID
不更新ID
断线重连时使用此ID

三、SSE与WebSocket对比

特性 SSE WebSocket
通信方向 单向(服务器→客户端) 双向
协议 HTTP 独立协议(ws://或wss://)
连接建立 简单(标准HTTP请求) 复杂(握手过程)
自动重连 内置支持 需手动实现
数据格式 仅文本 支持文本和二进制
浏览器支持 广泛支持 广泛支持
服务器资源 相对较低 相对较高
适用场景 实时通知、数据推送 聊天、游戏、双向交互

3.1 选择建议

选择SSE的场景:

  • 服务器单向推送数据
  • 需要自动重连机制
  • 推送的是文本数据
  • 实时通知、数据更新

选择WebSocket的场景:

  • 需要双向通信
  • 需要传输二进制数据
  • 低延迟要求极高的实时交互
  • 聊天、协作编辑、在线游戏

四、SSE与传统轮询对比

SSE
一次请求
持续推送
客户端
服务器
传统轮询
定时请求
返回数据
客户端
服务器

轮询的问题:

  • 频繁请求消耗服务器资源
  • 数据延迟(取决于轮询间隔)
  • 大量无效请求(无新数据时)

SSE的优势:

  • 一次连接,持续推送
  • 实时性高
  • 资源消耗低

五、Spring Boot集成SSE

5.1 基础实现

创建SSE控制器:

java 复制代码
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@RestController
@RequestMapping("/sse")
public class SseController {

    private final ExecutorService executor = Executors.newCachedThreadPool();

    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter streamEvents() {
        SseEmitter emitter = new SseEmitter(60_000L); // 超时时间60秒

        executor.execute(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    // 模拟业务处理
                    Thread.sleep(1000);

                    // 发送消息
                    emitter.send(SseEmitter.event()
                            .id(String.valueOf(i))
                            .name("message")
                            .data("消息内容: " + i)
                            .reconnectTime(5000L)); // 重连间隔5秒

                    System.out.println("发送消息: " + i);
                }

                // 发送完成事件
                emitter.complete();
            } catch (IOException | InterruptedException e) {
                emitter.completeWithError(e);
            }
        });

        return emitter;
    }
}

5.2 完整的SSE服务实现

SSE服务类:

java 复制代码
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

@Service
public class SseService {

    // 存储所有活跃的SSE连接
    private final ConcurrentHashMap<String, SseEmitter> emitters = new ConcurrentHashMap<>();

    // 存储用户ID到连接ID的映射
    private final ConcurrentHashMap<String, String> userConnections = new ConcurrentHashMap<>();

    // 存储订阅关系(用户ID -> 订阅的频道)
    private final ConcurrentHashMap<String, CopyOnWriteArraySet<String>> subscriptions = new ConcurrentHashMap<>();

    /**
     * 创建SSE连接
     */
    public SseEmitter createConnection(String userId) {
        String connectionId = generateConnectionId();

        // 设置超时时间为30分钟
        SseEmitter emitter = new SseEmitter(30 * 60 * 1000L);

        // 连接关闭时的处理
        emitter.onCompletion(() -> {
            System.out.println("连接完成: " + connectionId);
            removeConnection(connectionId);
        });

        emitter.onTimeout(() -> {
            System.out.println("连接超时: " + connectionId);
            removeConnection(connectionId);
        });

        emitter.onError((ex) -> {
            System.err.println("连接错误: " + connectionId);
            ex.printStackTrace();
            removeConnection(connectionId);
        });

        // 存储连接
        emitters.put(connectionId, emitter);
        userConnections.put(userId, connectionId);

        // 发送连接成功消息
        try {
            emitter.send(SseEmitter.event()
                    .name("connected")
                    .data("连接建立成功")
                    .id(connectionId));
        } catch (IOException e) {
            removeConnection(connectionId);
            throw new RuntimeException("建立连接失败", e);
        }

        return emitter;
    }

    /**
     * 向指定用户发送消息
     */
    public boolean sendToUser(String userId, String eventName, Object data) {
        String connectionId = userConnections.get(userId);
        if (connectionId == null) {
            return false;
        }

        return sendToConnection(connectionId, eventName, data);
    }

    /**
     * 向指定连接发送消息
     */
    public boolean sendToConnection(String connectionId, String eventName, Object data) {
        SseEmitter emitter = emitters.get(connectionId);
        if (emitter == null) {
            return false;
        }

        try {
            emitter.send(SseEmitter.event()
                    .name(eventName)
                    .data(data)
                    .id(String.valueOf(System.currentTimeMillis())));
            return true;
        } catch (IOException e) {
            System.err.println("发送消息失败: " + connectionId);
            removeConnection(connectionId);
            return false;
        }
    }

    /**
     * 向所有连接广播消息
     */
    public void broadcast(String eventName, Object data) {
        emitters.forEach((connectionId, emitter) -> {
            try {
                emitter.send(SseEmitter.event()
                        .name(eventName)
                        .data(data)
                        .id(String.valueOf(System.currentTimeMillis())));
            } catch (IOException e) {
                System.err.println("广播消息失败: " + connectionId);
                removeConnection(connectionId);
            }
        });
    }

    /**
     * 移除连接
     */
    private void removeConnection(String connectionId) {
        SseEmitter emitter = emitters.remove(connectionId);
        if (emitter != null) {
            emitter.complete();
        }

        // 移除用户映射
        userConnections.entrySet().removeIf(entry -> entry.getValue().equals(connectionId));

        System.out.println("连接已移除: " + connectionId + ", 当前连接数: " + emitters.size());
    }

    /**
     * 获取当前连接数
     */
    public int getConnectionCount() {
        return emitters.size();
    }

    /**
     * 生成连接ID
     */
    private String generateConnectionId() {
        return java.util.UUID.randomUUID().toString();
    }
}

控制器使用服务:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

@RestController
@RequestMapping("/api/sse")
public class SseController {

    @Autowired
    private SseService sseService;

    /**
     * 建立SSE连接
     */
    @GetMapping(value = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter connect(@RequestParam String userId) {
        System.out.println("用户连接: " + userId);
        return sseService.createConnection(userId);
    }

    /**
     * 向指定用户发送消息(测试用)
     */
    @PostMapping("/send")
    public String sendToUser(
            @RequestParam String userId,
            @RequestParam(defaultValue = "message") String eventName,
            @RequestBody Object data) {
        boolean success = sseService.sendToUser(userId, eventName, data);
        return success ? "发送成功" : "发送失败(用户未连接)";
    }

    /**
     * 广播消息(测试用)
     */
    @PostMapping("/broadcast")
    public String broadcast(
            @RequestParam(defaultValue = "message") String eventName,
            @RequestBody Object data) {
        sseService.broadcast(eventName, data);
        return "广播成功";
    }

    /**
     * 获取在线连接数
     */
    @GetMapping("/count")
    public int getConnectionCount() {
        return sseService.getConnectionCount();
    }
}

5.3 客户端代码示例

JavaScript客户端:

javascript 复制代码
// 建立SSE连接
const userId = 'user123';
const eventSource = new EventSource(`http://localhost:8080/api/sse/connect?userId=${userId}`);

// 监听连接成功事件
eventSource.addEventListener('connected', (event) => {
    console.log('连接成功:', event.data);
    console.log('连接ID:', event.lastEventId);
});

// 监听自定义消息事件
eventSource.addEventListener('message', (event) => {
    console.log('收到消息:', event.data);
    const data = JSON.parse(event.data);
    // 处理消息...
});

// 监听通知事件
eventSource.addEventListener('notification', (event) => {
    console.log('收到通知:', event.data);
    const notification = JSON.parse(event.data);
    // 显示通知...
});

// 监听错误
eventSource.onerror = (error) => {
    console.error('SSE连接错误:', error);
    // 浏览器会自动尝试重连
};

// 关闭连接
// eventSource.close();

带重试和心跳的客户端:

javascript 复制代码
class SseClient {
    constructor(url, options = {}) {
        this.url = url;
        this.options = {
            reconnectInterval: 5000,
            maxReconnectAttempts: 10,
            heartbeatInterval: 30000,
            ...options
        };
        this.reconnectAttempts = 0;
        this.eventSource = null;
        this.heartbeatTimer = null;
        this.isConnected = false;
    }

    connect() {
        this.eventSource = new EventSource(this.url);

        this.eventSource.onopen = () => {
            console.log('SSE连接已建立');
            this.isConnected = true;
            this.reconnectAttempts = 0;
            this.startHeartbeat();
        };

        this.eventSource.onerror = (error) => {
            console.error('SSE连接错误:', error);
            this.isConnected = false;
            this.stopHeartbeat();

            if (this.reconnectAttempts < this.options.maxReconnectAttempts) {
                this.reconnectAttempts++;
                console.log(`尝试重连 (${this.reconnectAttempts}/${this.options.maxReconnectAttempts})...`);
                setTimeout(() => this.connect(), this.options.reconnectInterval);
            } else {
                console.error('达到最大重连次数,停止重连');
            }
        };

        return this.eventSource;
    }

    addEventListener(event, callback) {
        if (this.eventSource) {
            this.eventSource.addEventListener(event, callback);
        }
    }

    startHeartbeat() {
        this.heartbeatTimer = setInterval(() => {
            if (this.isConnected) {
                console.log('心跳检测');
            }
        }, this.options.heartbeatInterval);
    }

    stopHeartbeat() {
        if (this.heartbeatTimer) {
            clearInterval(this.heartbeatTimer);
            this.heartbeatTimer = null;
        }
    }

    close() {
        this.stopHeartbeat();
        if (this.eventSource) {
            this.eventSource.close();
            this.eventSource = null;
        }
        this.isConnected = false;
    }
}

// 使用示例
const sseClient = new SseClient('http://localhost:8080/api/sse/connect?userId=user123', {
    reconnectInterval: 3000,
    maxReconnectAttempts: 5,
    heartbeatInterval: 60000
});

sseClient.connect();

sseClient.addEventListener('message', (event) => {
    console.log('收到消息:', event.data);
});

sseClient.addEventListener('notification', (event) => {
    console.log('收到通知:', event.data);
});

// 关闭连接
// sseClient.close();

六、实际应用场景

6.1 实时消息通知

建立SSE连接
实时推送
订单状态变更
新评论
系统通知
显示通知
用户
前端页面
通知服务
订单服务
评论服务
系统服务

实现示例:

java 复制代码
@Service
public class NotificationService {

    @Autowired
    private SseService sseService;

    /**
     * 发送订单状态变更通知
     */
    public void sendOrderNotification(String userId, Order order) {
        Notification notification = Notification.builder()
                .type("order")
                .title("订单状态更新")
                .message("您的订单 " + order.getOrderNo() + " 状态已变更为: " + order.getStatus())
                .data(order)
                .timestamp(LocalDateTime.now())
                .build();

        sseService.sendToUser(userId, "notification", notification);
    }

    /**
     * 发送评论通知
     */
    public void sendCommentNotification(String userId, Comment comment) {
        Notification notification = Notification.builder()
                .type("comment")
                .title("新评论")
                .message("用户 " + comment.getAuthor() + " 评论了你的文章")
                .data(comment)
                .timestamp(LocalDateTime.now())
                .build();

        sseService.sendToUser(userId, "notification", notification);
    }
}

6.2 实时数据大屏

java 复制代码
@RestController
@RequestMapping("/api/dashboard")
public class DashboardController {

    @Autowired
    private SseService sseService;

    @Autowired
    private DataService dataService;

    /**
     * 实时数据推送
     */
    @GetMapping(value = "/realtime", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter realtimeData() {
        String userId = "dashboard_" + System.currentTimeMillis();
        SseEmitter emitter = sseService.createConnection(userId);

        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(() -> {
            try {
                // 获取实时数据
                DashboardData data = dataService.getRealtimeData();

                // 推送数据
                sseService.sendToUser(userId, "dashboard", data);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, 0, 1, TimeUnit.SECONDS); // 每秒推送一次

        emitter.onCompletion(() -> scheduler.shutdown());
        emitter.onTimeout(() -> scheduler.shutdown());

        return emitter;
    }
}

6.3 实时日志监控

java 复制代码
@Service
public class LogMonitorService {

    @Autowired
    private SseService sseService;

    private final CopyOnWriteArraySet<String> subscribers = new CopyOnWriteArraySet<>();

    /**
     * 订阅日志
     */
    public SseEmitter subscribeLogs(String userId) {
        subscribers.add(userId);
        return sseService.createConnection(userId);
    }

    /**
     * 推送日志
     */
    public void pushLog(LogEntry logEntry) {
        if (!subscribers.isEmpty()) {
            subscribers.forEach(userId -> {
                sseService.sendToUser(userId, "log", logEntry);
            });
        }
    }

    /**
     * 取消订阅
     */
    public void unsubscribe(String userId) {
        subscribers.remove(userId);
    }
}

6.4 进度条推送

java 复制代码
@RestController
@RequestMapping("/api/task")
public class TaskController {

    @Autowired
    private SseService sseService;

    /**
     * 执行长任务并推送进度
     */
    @GetMapping(value = "/execute", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter executeTask(@RequestParam String userId, @RequestParam String taskId) {
        SseEmitter emitter = sseService.createConnection(userId);

        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.execute(() -> {
            try {
                // 推送任务开始
                sseService.sendToUser(userId, "progress", Progress.builder()
                        .taskId(taskId)
                        .stage("初始化")
                        .percentage(0)
                        .message("任务开始")
                        .build());

                // 模拟任务执行
                for (int i = 0; i <= 100; i += 10) {
                    Thread.sleep(1000);

                    sseService.sendToUser(userId, "progress", Progress.builder()
                            .taskId(taskId)
                            .stage("处理中")
                            .percentage(i)
                            .message("正在处理... " + i + "%")
                            .build());
                }

                // 任务完成
                sseService.sendToUser(userId, "progress", Progress.builder()
                        .taskId(taskId)
                        .stage("完成")
                        .percentage(100)
                        .message("任务完成")
                        .build());

                sseService.sendToUser(userId, "complete", TaskResult.builder()
                        .taskId(taskId)
                        .success(true)
                        .message("任务执行成功")
                        .build());

                emitter.complete();
            } catch (Exception e) {
                sseService.sendToUser(userId, "error", TaskResult.builder()
                        .taskId(taskId)
                        .success(false)
                        .message("任务执行失败: " + e.getMessage())
                        .build());
                emitter.completeWithError(e);
            } finally {
                executor.shutdown();
            }
        });

        return emitter;
    }
}

七、生产环境最佳实践

7.1 连接管理

java 复制代码
@Service
public class SseConnectionManager {

    private final ConcurrentHashMap<String, SseEmitter> emitters = new ConcurrentHashMap<>();
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    @PostConstruct
    public void init() {
        // 定期清理过期连接
        scheduler.scheduleAtFixedRate(this::cleanExpiredConnections, 5, 5, TimeUnit.MINUTES);
    }

    private void cleanExpiredConnections() {
        emitters.forEach((id, emitter) -> {
            try {
                // 发送心跳检测
                emitter.send(SseEmitter.event().name("heartbeat").data("ping"));
            } catch (IOException e) {
                // 发送失败,移除连接
                removeConnection(id);
            }
        });
    }

    @PreDestroy
    public void destroy() {
        scheduler.shutdown();
        emitters.forEach((id, emitter) -> emitter.complete());
        emitters.clear();
    }
}

7.2 消息队列集成

java 复制代码
@Service
public class SseMessageQueueService {

    @Autowired
    private SseService sseService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 发送消息到队列
     */
    public void sendMessageToQueue(String userId, String eventName, Object data) {
        SseMessage message = SseMessage.builder()
                .userId(userId)
                .eventName(eventName)
                .data(data)
                .timestamp(LocalDateTime.now())
                .build();

        rabbitTemplate.convertAndSend("sse.exchange", "sse.routing", message);
    }

    /**
     * 从队列消费消息并推送
     */
    @RabbitListener(queues = "sse.queue")
    public void handleSseMessage(SseMessage message) {
        sseService.sendToUser(message.getUserId(), message.getEventName(), message.getData());
    }
}

7.3 分布式环境支持

java 复制代码
@Service
public class DistributedSseService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private SseService sseService;

    /**
     * 发布消息到Redis
     */
    public void publishMessage(String channel, String eventName, Object data) {
        SseMessage message = SseMessage.builder()
                .eventName(eventName)
                .data(data)
                .timestamp(LocalDateTime.now())
                .build();

        redisTemplate.convertAndSend(channel, message);
    }

    /**
     * 订阅Redis消息
     */
    @PostConstruct
    public void subscribeChannels() {
        redisTemplate.getConnectionFactory()
                .getConnection()
                .subscribe((message, pattern) -> {
                    try {
                        SseMessage sseMessage = JsonUtils.fromJson(
                                new String(message.getBody()),
                                SseMessage.class
                        );

                        // 广播给当前服务器的所有连接
                        sseService.broadcast(sseMessage.getEventName(), sseMessage.getData());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }, "sse.channel.*");
    }
}

八、性能优化建议

8.1 连接数限制

java 复制代码
@Configuration
public class SseConfig {

    @Bean
    public SseService sseService() {
        return new SseService() {
            private static final int MAX_CONNECTIONS = 10000;
            private final AtomicInteger connectionCount = new AtomicInteger(0);

            @Override
            public SseEmitter createConnection(String userId) {
                if (connectionCount.get() >= MAX_CONNECTIONS) {
                    throw new RuntimeException("连接数已达上限");
                }
                connectionCount.incrementAndGet();
                return super.createConnection(userId);
            }

            @Override
            public void removeConnection(String connectionId) {
                super.removeConnection(connectionId);
                connectionCount.decrementAndGet();
            }
        };
    }
}

8.2 消息压缩

java 复制代码
@RestController
@RequestMapping("/api/sse")
public class CompressedSseController {

    @GetMapping(value = "/compressed", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter streamCompressedEvents(@RequestParam String userId) {
        SseEmitter emitter = new SseEmitter(60_000L);

        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.execute(() -> {
            try {
                for (int i = 0; i < 100; i++) {
                    // 原始数据
                    Map<String, Object> data = new HashMap<>();
                    data.put("id", i);
                    data.put("timestamp", System.currentTimeMillis());
                    data.put("content", "这是一条测试消息");

                    // 序列化为JSON
                    String json = JsonUtils.toJson(data);

                    // 使用gzip压缩
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    try (GZIPOutputStream gzip = new GZIPOutputStream(bos)) {
                        gzip.write(json.getBytes(StandardCharsets.UTF_8));
                    }

                    // 发送压缩后的数据
                    emitter.send(SseEmitter.event()
                            .name("message")
                            .data(Base64.getEncoder().encodeToString(bos.toByteArray()))
                            .comment("gzip"));

                    Thread.sleep(1000);
                }
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            } finally {
                executor.shutdown();
            }
        });

        return emitter;
    }
}

九、常见问题与解决方案

9.1 连接超时问题

问题: SSE连接在一段时间后自动断开

解决方案:

java 复制代码
// 1. 设置合理的超时时间
SseEmitter emitter = new SseEmitter(30 * 60 * 1000L); // 30分钟

// 2. 定期发送心跳保持连接
scheduledExecutor.scheduleAtFixedRate(() -> {
    try {
        emitter.send(SseEmitter.event().name("heartbeat").data("ping"));
    } catch (IOException e) {
        emitter.completeWithError(e);
    }
}, 0, 30, TimeUnit.SECONDS); // 每30秒发送一次心跳

9.2 消息丢失问题

问题: 客户端断线重连后丢失部分消息

解决方案:

java 复制代码
@Service
public class ReliableSseService {

    // 存储消息历史
    private final ConcurrentHashMap<String, LinkedList<SseMessage>> messageHistory = new ConcurrentHashMap<>();

    private static final int MAX_HISTORY_SIZE = 100;

    /**
     * 发送消息并保存历史
     */
    public void sendWithHistory(String userId, String eventName, Object data) {
        SseMessage message = SseMessage.builder()
                .id(generateMessageId())
                .eventName(eventName)
                .data(data)
                .timestamp(LocalDateTime.now())
                .build();

        // 保存到历史
        messageHistory.computeIfAbsent(userId, k -> new LinkedList<>())
                .addLast(message);

        // 限制历史大小
        if (messageHistory.get(userId).size() > MAX_HISTORY_SIZE) {
            messageHistory.get(userId).removeFirst();
        }

        // 发送消息
        sseService.sendToUser(userId, eventName, data);
    }

    /**
     * 重连时发送历史消息
     */
    public void sendHistoryOnReconnect(String userId, String lastEventId) {
        LinkedList<SseMessage> history = messageHistory.get(userId);
        if (history != null) {
            history.stream()
                    .filter(msg -> msg.getId().compareTo(lastEventId) > 0)
                    .forEach(msg -> sseService.sendToUser(userId, msg.getEventName(), msg.getData()));
        }
    }
}

9.3 Nginx代理配置

问题: 通过Nginx代理后SSE连接不稳定

解决方案:

nginx 复制代码
location /sse {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header X-Accel-Buffering no;  # 禁用缓冲
    proxy_cache off;  # 禁用缓存
    proxy_read_timeout 3600s;  # 增加超时时间
    proxy_send_timeout 3600s;
    chunked_transfer_encoding on;
}

十、总结

SSE作为一种轻量级的服务器推送技术,在以下场景中具有明显优势:

适用场景:

  • 实时消息通知
  • 数据大屏实时更新
  • 进度条推送
  • 日志监控
  • 股票/价格实时更新
  • 新闻推送

不适用场景:

  • 需要双向通信的场景(使用WebSocket)
  • 需要传输二进制数据的场景(使用WebSocket)
  • 需要低延迟高频交互的场景(使用WebSocket)

核心优势:

  • 实现简单,基于标准HTTP
  • 自动重连机制
  • 服务器资源消耗低
  • 浏览器原生支持

注意事项:

  • 注意连接超时和心跳保持
  • 生产环境需要考虑分布式支持
  • 合理控制连接数和消息频率
  • 做好错误处理和重连机制

SSE是实时通信领域的重要工具,掌握其原理和实现方式,能够帮助我们构建更加实时和高效的Web应用。

相关推荐
dreamxian2 小时前
苍穹外卖day07
java·spring boot·后端·spring·mybatis
流水武qin2 小时前
SpringAI 使用 RAG
java·spring boot·spring·ai
wayz112 小时前
正则表达式:从入门到精通
java·python·正则表达式·编辑器
网安2311石仁杰2 小时前
深入解析OWASP ZAP:从软件工程视角看安全扫描器的架构设计
java·安全·软件工程
bbq粉刷匠2 小时前
Java--多线程--线程安全3
java·开发语言
霍格沃兹测试学院-小舟畅学2 小时前
LangChain + DeepSeek 实战拆解:从 LCEL 到智能体,如何真正“做出”一个可控 AI 系统?
java·人工智能·langchain
96772 小时前
java数据类型解析以及相关八股文的题 String 到底是基本类型还是引用类型?
java·开发语言·python
果果燕2 小时前
网络编程第一天学习笔记(重点:UDP 协议)
网络
码界奇点2 小时前
基于Java GUI和Access数据库的图书馆管理系统设计与实现
java·开发语言·数据库·毕业设计·源代码管理