6.13Server-Sent Events

随着人工智能聊天机器人 ChatGPT 的快速流行,其用户界面中模拟打字机效果的对话设计给人留下了深刻印象。这种生动逼真的交互体验,实际上是通过一种称为“服务器发送事件”(Server-Sent Events, SSE)的技术实现的。

Server-Sent Events 单向通信#

随着人工智能聊天机器人 ChatGPT 的快速流行,其用户界面中模拟打字机效果的对话设计给人留下了深刻印象。这种生动逼真的交互体验,实际上是通过一种称为“服务器发送事件”(Server-Sent Events, SSE)的技术实现的。

Server-Sent Events 是一种允许服务器主动向客户端(通常是浏览器)发送实时更新数据的通信技术。与传统的客户端请求-服务器响应模式不同,SSE 实现了服务器到客户端的单向、异步通信,从而无需客户端不断轮询服务器以获取最新数据。 这种技术极大地减轻了服务器的负担,并提高了数据传输的效率和实时性。

Server-Sent Events 的应用场景:

  1. 实时通知:可以用来实现实时的消息提醒或通知系统,如社交网络上的新消息提示或邮件到达通知。
  2. 数据流更新:对于需要持续更新的数据,如股票价格、天气信息或体育比赛结果,SSE 能够提供即时的数据更新。
  3. 进度报告:在执行耗时较长的任务时,比如文件上传或复杂计算过程中,SSE 可以用来向客户端报告任务的进度。
  4. 日志和监控:在开发和运维领域,SSE 可用于实时显示日志文件的变化或监控系统的健康状态。

以下示例展示了如何使用 Server-Sent Events 向服务器获取数据:

cs
await httpRemoteService.ServerSentEventsAsync("https://localhost:7044/HttpRemote/Events"   // 接收到数据时的操作   , async (data, token) =>   {       Console.WriteLine(data.Data);       await Task.CompletedTask;   }, cancellationToken: cancellationToken);// 使用构建器模式await httpRemoteService.SendAsync(HttpRequestBuilder   .ServerSentEvents("https://localhost:7044/HttpRemote/Events"   // 接收到数据时的操作   , async (data, token) =>   {       Console.WriteLine(data.Data);       await Task.CompletedTask;   }), cancellationToken: cancellationToken);

Server-Sent Events 也支持以 IAsyncEnumerable<ServerSentEventsData> 的方式消费数据,让你可以使用 await foreach 来迭代每个轮询响应:

cs
await foreach (var data in httpRemoteService.ServerSentEventsAsAsyncEnumerable("https://localhost:7044/HttpRemote/Events", cancellationToken: cancellationToken)){    Console.WriteLine(data.Data);}// 使用构建器模式await foreach (var data in httpRemoteService.SendAsAsyncEnumerable(HttpRequestBuilder.ServerSentEvents("https://localhost:7044/HttpRemote/Events"), cancellationToken)){    Console.WriteLine(data.Data);}

data 参数的类型为 ServerSentEventsData,包含以下属性:

  • 属性
    • Event:事件类型(string 类型)。
    • Data:消息(string 类型)。
    • RawLine:原始消息行(string 类型)。
    • Id:事件 IDstring 类型)。
    • Retry:重新连接的时间(以毫秒为单位的 int 类型)。
    • CustomFields:自定义的字段数据(IReadOnlyCollection<KeyValuePair<string, string>> 类型)。

您还可以监听连接成功和发送异常时的事件:

cs
await httpRemoteService.ServerSentEventsAsync("https://localhost:7044/HttpRemote/Events"   // 接收到数据时的操作   , async (data, token) =>   {       Console.WriteLine(data.Data);       await Task.CompletedTask;   }, builder => builder   // 连接打开时操作   .SetOnOpen(() =>   {       Console.WriteLine("连接成功。");   })   // 连接未打开时操作   .SetOnError((ex) =>   {       Console.WriteLine("连接错误。" + ex.Message);   }), cancellationToken: cancellationToken);// 使用构建器模式await httpRemoteService.SendAsync(HttpRequestBuilder   .ServerSentEvents("https://localhost:7044/HttpRemote/Events"   // 接收到数据时的操作   , async (data, token) =>   {       Console.WriteLine(data.Data);       await Task.CompletedTask;   })   // 连接打开时操作   .SetOnOpen(() =>   {       Console.WriteLine("连接成功。");   })   // 连接未打开时操作   .SetOnError((ex) =>   {       Console.WriteLine("连接错误。" + ex.Message);   }), cancellationToken: cancellationToken);

Server-Sent Events 特别适合那些需要服务器向客户端发送更新,但客户端不需要频繁向服务器发送请求的应用场景。无论是用于实时更新数据、提供进度报告还是实现简单的通知系统,SSE 都是一个值得考虑的选择。

HttpServerSentEventsBuilder 构建器#

HttpServerSentEventsBuilder 构建器是框架提供专门用来接收服务器 Server-Sent Events 推送事件所需的各项设置。HttpServerSentEventsBuilder 的构造函数是私有的,因此无法直接使用 new 关键字进行实例化,不过,框架提供了 HttpRequestBuilder.ServerSentEvents 的多个静态重载方法创建 HttpServerSentEventsBuilder 的实例。

cs
HttpRequestBuilder.ServerSentEvents(requestUri, onMessage, configure);  // 默认 GET 请求HttpRequestBuilder.ServerSentEvents(httpMethod, requestUri, onMessage, configure);HttpRequestBuilder.ServerSentEvents(requestUri, configure); // 默认 GET 请求HttpRequestBuilder.ServerSentEvents(httpMethod, requestUri, configure);

此外,HttpServerSentEventsBuilder 包含以下配置功能:

cs
// 默认为 GET 请求HttpRequestBuilder.ServerSentEvents("https://localhost:7044/HttpRemote/Events"    , async (data, token) =>    {       Console.WriteLine(data.Data);       await Task.CompletedTask;    })    // 设置默认重新连接的间隔时间,默认为 2 秒    .SetDefaultRetryInterval(2000)  // 单位毫秒    // 设置最大重试次数,默认为 100 次    .SetMaxRetries(500)    // 设置用于在与事件源的连接打开时的操作    .SetOnOpen(() => {})    // 设置用于在从事件源接收到数据时的操作    // .SetOnMessage(async (data, token) => {})  // 可通过初始时传入    // 设置用于在事件源连接未能打开时的操作    .SetOnError(exception => {})    // 设置 Server-Sent Events 事件处理程序    .SetEventHandler<CustomServerSentEventsEventHandler>()    .SetEventHandler(typeof(CustomServerSentEventsEventHandler))    // 设置是否自动修正请求方法    // 若为 true 则当请求为 GET 或 HEAD 且包含请求内容时,自动将方法改为 POST;默认值为 true    .SetAutoCorrectMethod(true)    // 设置 HttpRequestBuilder 实例    .With(builder => {}));  // 支持更多扩展

在通过 HttpRequestBuilder.ServerSentEvents 方法成功构建 HttpServerSentEventsBuilder 实例后,您可以利用 SendSendAsyncSendAsAsyncEnumerable 来执行发送操作。

cs
httpRemoteService.Send(httpServerSentEventsBuilder, cancellationToken);await httpRemoteService.SendAsync(httpServerSentEventsBuilder, cancellationToken);await foreach (var data in httpRemoteService.SendAsAsyncEnumerable(httpServerSentEventsBuilder, cancellationToken)){    // 处理每个数据}

Server-Sent Events 事件处理程序#

IHttpServerSentEventsEventHandler 接口允许您定义接收服务器 Server-Sent Events 推送事件的预处理操作。通过实现该接口,您可以创建自定义的 Server-Sent Events 事件处理程序,例如 CustomServerSentEventsEventHandler 类:

cs
public class CustomServerSentEventsEventHandler : IHttpServerSentEventsEventHandler{    // 用于在与事件源的连接打开时的操作    void OnOpen();    // 用于在从事件源接收到数据时的操作    Task OnMessageAsync(ServerSentEventsData serverSentEventsData, CancellationToken cancellationToken);    // 用于在事件源连接未能打开时的操作    void OnError(Exception exception);}

要在应用程序中启用此处理程序,请在 Startup.csProgram.cs 文件中注册 CustomServerSentEventsEventHandler 服务:

cs
services.TryAddSingleton<CustomServerSentEventsEventHandler>();

接下来,您可以在构建 HTTP 请求时指定此处理程序:

cs
HttpRequestBuilder.ServerSentEvents("https://localhost:7044/HttpRemote/Events"    , async (data, token) =>    {       Console.WriteLine(data.Data);       await Task.CompletedTask;    })    .SetEventHandler<CustomServerSentEventsEventHandler>();HttpRequestBuilder.ServerSentEvents("https://localhost:7044/HttpRemote/Events"    , async (data, token) =>    {       Console.WriteLine(data.Data);       await Task.CompletedTask;    })    .SetEventHandler(typeof(CustomServerSentEventsEventHandler));    // 使用类型的方式