在C#中实现发送服务器已发送事件(无ASP.NET / MVC / ...)

mh1*_*166 2 c# server-sent-events

对于一个项目,我需要在C#应用程序中实现SSE(服务器发送事件)。尽管这听起来很简单,但是我不知道如何解决这个问题。

由于我是C#的新手(尽管通常不是编程新手),所以我参观了Google,并尝试查找一些示例代码。到目前为止,我可以学习使用C#构建HTTP Server或使用服务器发送的事件。但是我没有发现发送SSE的信息。

我要设法解决的问题:如何继续通过传入请求发送更新的数据?通常,您会收到请求,做您的事情并回复。完成,连接已关闭。但是在这种情况下,每次我的应用程序中的事件触发时,我都希望“粘”在响应流上并通过它发送新的数据。

对我而言,问题在于这种基于事件的方法:它不是基于间隔的轮询和更新。而是该应用程序的运行方式类似于“嘿,发生了什么事。我真的应该告诉你!”

TL; DR:如何保持响应流并发送更新-不是基于循环或计时器,而是每当某些事件触发时?

另外,在我忘记之前:我知道,那里有图书馆就是这样做的。但是从目前为止我所看到的(以及我所了解的;如果我错了,请纠正我),这些解决方案取决于ASP.NET/MVC/您的名字。在编写“普通” C#应用程序时,我认为我不满足这些要求。

cku*_*uri 6

对于轻量级服务器,我将使用OWIN自托管WebAPI(https://docs.microsoft.com/en-us/aspnet/web-api/overview/hosting-aspnet-web-api/use-owin- to-self-host-web-api)。

一个简单的服务器发送的事件服务器操作基本上如下所示:

public class EventController : ApiController
  {
    public HttpResponseMessage GetEvents(CancellationToken clientDisconnectToken)
    {
      var response = Request.CreateResponse();
      response.Content = new PushStreamContent(async (stream, httpContent, transportContext) =>
      {
        using (var writer = new StreamWriter(stream))
        {
          using (var consumer = new BlockingCollection<string>())
          {
            var eventGeneratorTask = EventGeneratorAsync(consumer, clientDisconnectToken);
            foreach (var @event in consumer.GetConsumingEnumerable(clientDisconnectToken))
            {
              await writer.WriteLineAsync("data: " + @event);
              await writer.WriteLineAsync();
              await writer.FlushAsync();
            }
            await eventGeneratorTask;
          }
        }
      }, "text/event-stream");
      return response;
    }

    private async Task EventGeneratorAsync(BlockingCollection<string> producer, CancellationToken cancellationToken)
    {
      try
      {
        while (!cancellationToken.IsCancellationRequested)
        {
          producer.Add(DateTime.Now.ToString(), cancellationToken);
          await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
        }
      }
      finally
      {
        producer.CompleteAdding();
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)

这里的重要部分是PushStreamContent,它基本上只是发送HTTP标头,然后保持连接开放以在可用时写入数据。

在我的示例中,事件是在额外任务中生成的,该任务被赋予生产者-消费者集合,并添加事件(此处为每秒的当前时间)(如果集合可用)。每当有新事件到达时,都会自动通知GetConsumingEnumerable。然后,将新事件以正确的服务器发送事件格式写入流中并进行刷新。在实践中,您将需要每分钟左右发送一些伪ping事件,因为长时间处于打开状态而没有通过它们发送数据的流将被OS /框架关闭。

用于测试此内容的示例客户端代码如下:

用异步方法编写以下代码。

using (var client = new HttpClient())
{
  using (var stream = await client.GetStreamAsync("http://localhost:9000/api/event"))
  {
    using (var reader = new StreamReader(stream))
    {
      while (true)
      {
        Console.WriteLine(reader.ReadLine());
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

  • @mason我必须做一些类似于客户端实现服务器发送事件EventSource(https://html.spec.whatwg.org/multipage/server-sent-events.html)的事情,并且我无法控制客户端实现。除此之外,SignalR 是流媒体场景的一个很好的工具。 (3认同)

Tim*_*ver 5

这听起来很适合SignalR。注意 SignalR 是 ASP.NET 系列的一部分,但这不需要 ASP.NET 框架 (System.Web) 或 IIS,如注释中所述。

澄清一下,SignalR 是 ASP.NET 的一部分。根据他们的网站:

ASP.NET 是一个开源 Web 框架,用于使用 .NET 构建现代 Web 应用程序和服务。ASP.NET 创建基于 HTML5、CSS 和 JavaScript 的网站,这些网站简单、快速并且可以扩展到数百万用户。

SignalR 对 System.Web 或 IIS 没有硬依赖。

您可以自行托管 ASP.Net 应用程序(请参阅https://learn.microsoft.com/en-us/aspnet/signalr/overview/deployment/tutorial-signalr-self-host)。如果您使用.net core,它实际上默认是自托管的,并作为普通控制台应用程序运行。