为什么您的网站至今仍无法与用户实时“对话”?
\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
在实践中,C# SSE 用于:
\n- \n
- 通知推送(直接推送到浏览器的推送通知) \n
- 服务器监控(带有实时指标的仪表板) \n
- 日志流式传输(例如,浏览器中的 tail -f) \n
- AI 生成内容流式传输(类似 ChatGPT 模型的逐步响应输出) \n
SSE 如何工作:一行协议
\n\n一切伟大的事物都很简单。服务器向客户端发送一个具有特殊格式的文本流:
\n\ndata: 你好,世界!\\n\\n\ndata: {"message": "JSON 也能工作"}\\n\\n\nevent: customEvent\\n\ndata: 命名事件\\n\\n\n\n每条消息以 data: 开头,以两个换行符结束。您可以指定 event: 用于自定义事件类型,id: 用于标识符,以及 retry: 用于重新连接超时。
客户端使用 JavaScript 进行订阅:
\n\nconst 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\nC# 中的服务器发送事件:在 ASP.NET Core 上实现
\n\n让我们终于开始编写代码。在 C# 中组织 SSE 的最简单方法是在 ASP.NET Core 中创建一个端点,该端点不完成响应,而是分块写入。
\n\n基本 SSE 端点
\n\nusing 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",