C# 中的服务器发送事件:无需 WebSocket 的响应式 Web

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么您的网站至今仍无法与用户实时“对话”?

\n\n

想象一下:您打开一个股票交易终端,美元汇率自动更新。或者您登录管理后台,看到注册用户数量在眼前实时增长。又或者是一个聊天室,消息无需不断“刷新”页面就能送达。

\n\n

过去,要实现这一点,您要么必须使用 WebSocket(复杂、昂贵、并非总是必需),要么使用 长轮询(一种会耗尽手机电量的权宜之计)。但还有第三种方式——C# 中的服务器发送事件。

\n\n

今天,我们将剖析一项技术,它允许服务器主动“联系”客户端并传输数据。无需多余的代码,无需大量的 JavaScript,无需为代理服务器头疼。只有 C#,只有硬核。

\n\n

什么是服务器发送事件 (SSE) 以及为什么它不是“穷人的协议”

\n\n

服务器发送事件(或 SSE)是一种 HTML5 标准,它允许服务器通过普通的 HTTP 连接向客户端发送流式数据。客户端订阅事件,而服务器只需写入 Response.Body。仅此而已。

\n\n

与 WebSocket 的主要区别:SSE 是单向通道(服务器 → 客户端)。您不需要向服务器发回数据?太好了,SSE 是您的选择。同时:

\n\n
    \n
  • 基于普通的 HTTP/1.1 和 HTTP/2 工作
  • \n
  • 连接断开时自动重新连接(内置于浏览器 EventSource 中)
  • \n
  • 客户端无需额外的库(除了标准的 JavaScript)
  • \n
  • 能通过任何代理和负载均衡器(与 WebSocket 不同)
  • \n
\n\n

在实践中,C# SSE 用于:

\n
    \n
  • 通知推送(直接推送到浏览器的推送通知)
  • \n
  • 服务器监控(带有实时指标的仪表板)
  • \n
  • 日志流式传输(例如,浏览器中的 tail -f)
  • \n
  • AI 生成内容流式传输(类似 ChatGPT 模型的逐步响应输出)
  • \n
\n\n

SSE 如何工作:一行协议

\n\n

一切伟大的事物都很简单。服务器向客户端发送一个具有特殊格式的文本流:

\n\n
data: 你好,世界!\\n\\n\ndata: {"message": "JSON 也能工作"}\\n\\n\nevent: customEvent\\n\ndata: 命名事件\\n\\n
\n\n

每条消息以 data: 开头,以两个换行符结束。您可以指定 event: 用于自定义事件类型,id: 用于标识符,以及 retry: 用于重新连接超时。

\n\n

客户端使用 JavaScript 进行订阅:

\n\n
const source = new EventSource("/api/stream");\nsource.onmessage = (event) => {\n    console.log("收到:", event.data);\n};\nsource.addEventListener("customEvent", (event) => {\n    console.log("自定义事件:", event.data);\n});
\n\n

这适用于所有现代浏览器。无需 npm 包,无需 WebSocket polyfill。

\n\n

C# 中的服务器发送事件:在 ASP.NET Core 上实现

\n\n

让我们终于开始编写代码。在 C# 中组织 SSE 的最简单方法是在 ASP.NET Core 中创建一个端点,该端点不完成响应,而是分块写入。

\n\n

基本 SSE 端点

\n\n
using Microsoft.AspNetCore.Mvc;\n\n[ApiController]\n[Route("api")]\npublic class StreamController : ControllerBase\n{\n    [HttpGet("stream")]\n    public async Task Get(CancellationToken cancellationToken)\n    {\n        Response.Headers.Append("Content-Type", "text/event-stream");\n        Response.Headers.Append("Cache-Control", "no-cache");\n        Response.Headers.Append("Connection", "keep-alive");\n\n        for (int i = 0; i < 10; i++)\n        {\n            if (cancellationToken.IsCancellationRequested)\n                break;\n\n            // SSE 格式: data: ... \\n\\n\n            await Response.WriteAsync($"data: 消息 #{i}\\n\\n",                

博客

书籍推荐