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

时间:2017-06-30 17:25:06

标签: c# server-sent-events

对于项目,我需要在C#应用程序中实现SSE(Server Sent Events)。虽然这听起来很容易,但我不知道如何解决这个问题。

由于我是C#的新手(虽然,对于一般的编程并不陌生)我去了一次游览Google,并试图寻找一些示例代码。从我目前所见,我可以学习使用C#或使用服务器发送事件来构建HTTP服务器。但我发现没有发送SSE。

我想要解决的问题是:如何继续通过传入请求发送更新数据?通常,您会收到请求,做您的事情并回复。完成,连接关闭。但在这种情况下,我希望能够“坚持”#34;每当我的应用程序中的事件触发时,响应流并发送新数据。

对我来说,问题在于这种基于事件的方法:它不是一些基于intervall的轮询和更新。相反,应用程序就像#34;嘿,发生了什么。我真的应该告诉你一些事情!"

  

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

另外,在我忘记之前:我知道,那里有图书馆。但是从我到目前为止所看到的情况(以及我所理解的情况;如果我错了,请纠正我),这些解决方案依赖于ASP.NET / MVC /您的名字。因为我只是写一个"普通" C#应用程序,我不认为我符合这些要求。

2 个答案:

答案 0 :(得分:4)

对于轻量级服务器,我会使用OWIN selfhost 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();
      }
    }
  }

这里的重要部分是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());
      }
    }
  }
}

答案 1 :(得分:2)

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

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

  

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

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

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