通过sse推送服务器消息
SSE(Sever-Sent Event)服务器推送事件
SSE作用非常简单, 就是让服务端向客户端推送消息
乍一看和 websocket 作用相同, 但实际上两者有不少区别
- SSE 基于HTTP协议, 基本上所有后端服务都支持, 而 websocket 是一个独立的协议, 各种支持可能不是很全
- SSE 只能由 服务器向浏览器推送, websocket 可以进行双向通信, 因此两者的消耗也会有差距
- SSE 默认支持断线重连, 相对来说更加省心, websocket 则要自己实现
我暂时了解到的就这些了, 本文就简单介绍下 SSE 的使用
使用中间件的简单做法
using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(action => action.AddDefaultPolicy(p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()));
var app = builder.Build();
app.UseCors();
app.Use(async (context, next) =>
{
// 跳过非 /connect/sse 的请求
if (context.Request.Path.Value.ToLower() == "/connect/sse")
{
// 记录请求的时间
var flag = DateTime.Now;
// 长连接
context.Response.Headers.Add("Connection", "keep-alive");
// 必须将 content-type 设置为 text/event-stream
context.Response.Headers.Add("Content-Type", "text/event-stream");
// 60秒后自动断开连接
while ((DateTime.Now - flag).TotalSeconds < 60)
{
// 向response中的data中写入时间
await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes($"data:{DateTime.Now.ToString()}\n\n"));
await Task.Delay(666);
}
}
await next();
});
app.Run();
使用中间件建立sse连接可能是最简单的
只需要配置跨域和一个中间件即可
中间件修改 response
的 headers
- 将
Connection
设置为keep-alive
长连接 - 将
Content-Type
设置为text/event-stream
, 这是 sse 连接必须的
上面是服务端的代码, 客户端的代码相对来说也非常简单
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
var source = null;
function connect () {
if ('EventSource' in window) {
if (source != null)
source.close();
source = new EventSource(`http://localhost:5000/Connect/SSE`);
var result = document.getElementById('result');
source.onopen = () => result.innerHTML += "SSE通道已建立...<br/>";
source.onmessage = event => result.innerHTML += "Message: " + event.data + "<br/>";
source.onerror = event => {
result.innerHTML += "SSE通道发生错误<br/>";
source.close();
};
}
}
function closeEvent () {
document.getElementById("result").innerHTML += "主动关闭sse连接"+"<br/>";
source.close();
}
</script>
</head>
<body>
<div>
<button onclick="connect()">打开连接</button>
<button onclick="closeEvent()">关闭连接</button>
</div>
<div id="result"></div>
</body>
</html>
通过 new EventSource
创建连接, 然后为对应的事件绑定实现
这里的例子就只是简单地将接收到的 data
加到 result
中
点击 打开连接
的按钮之后, 就会开始尝试建立连接, 当创建成功之后会触发 onopen事件
当服务器推送消息的时候会触发 onmessage事件
本文的例子是显示服务器时间
sse的消息主要由4个部分组成 id,event,message,retry
- id: 数据的标识
- event: 事件的类型
- message: 具体的消息内容
- retry: 断开重连的时间间隔
可能比较常用的就是 event
和 message
使用 controller 的常规做法
可能大多数情况下写一个 controller
会优先于写 中间件, 两者的做法大致上没什么区别
都要先 修改headers, 然后向response中写入消息
using System.Text;
using Microsoft.AspNetCore.Mvc;
namespace ssetest.Controllers;
[ApiController]
[Route("[controller]")]
public class ConnectController : ControllerBase
{
[HttpGet("SSE")]
public async Task SSEAsync()
{
Response.Headers.Add("Connection", "keep-alive");
Response.Headers.Add("Content-Type", "text/event-stream");
var flag = DateTime.Now;
while ((DateTime.Now - flag).TotalSeconds < 60)
{
await Response.Body.WriteAsync(Encoding.UTF8.GetBytes($"data:{DateTime.Now}\n\n"));
await Task.Delay(666);
}
}
}
controller
中的实现与中间件中基本上没什么区别
html
的页面部分也不需要做修改
自定义事件
sse 可以自定义事件
默认情况下所有的消息都触发 onmessage事件
但是在复杂的使用环境下可能需要对多种消息做区分, 这时候就可以通过自定义消息实现
using System.Text;
using Microsoft.AspNetCore.Mvc;
namespace ssetest.Controllers;
[ApiController]
[Route("[controller]")]
public class ConnectController : ControllerBase
{
[HttpGet("SSE")]
public async Task SSEAsync()
{
Response.Headers.Add("Connection", "keep-alive");
Response.Headers.Add("Content-Type", "text/event-stream");
var flag = DateTime.Now;
while ((DateTime.Now - flag).TotalSeconds < 60)
{
await Response.Body.WriteAsync(Encoding.UTF8.GetBytes($"event:pushtime\ndata:{DateTime.Now}\n\n"));
await Task.Delay(666);
}
}
}
然后在 html
页面中添加一个事件监听
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
var source = null;
function connect () {
if ('EventSource' in window) {
if (source != null)
source.close();
source = new EventSource(`http://localhost:5135/Connect/SSE`);
source.addEventListener('pushtime', function (event) {
document.getElementById("result").innerHTML += "pushtime: " + event.data + "<br/>";
});
}
}
function closeEvent () {
document.getElementById("result").innerHTML += "主动关闭sse连接"+"<br/>";
source.close();
}
</script>
</head>
<body>
<div>
<button onclick="connect()">打开连接</button>
<button onclick="closeEvent()">关闭连接</button>
</div>
<div id="result"></div>
</body>
</html>
这样就可以通过自定义不同的事件区分不同的消息