在 C# 中实现发送服务器发送的事件(没有 ASP.NET / MVC / ...)
Implement sending Server Sent Events in C# (no ASP.NET / MVC / ...)
对于一个项目,我需要在 C# 应用程序中实现 SSE (Server Sent Events)。虽然这听起来很简单,但我不知道如何解决这个问题。
由于我是 C# 的新手(尽管对一般编程来说并不陌生),我游览了 Google 并试图寻找一些示例代码。根据我目前所见,我可以学习使用 C# 或 consume 服务器发送的事件来构建 HTTP 服务器。但是我没有找到关于发送 SSE 的信息。
我想解决的问题:我怎样才能通过传入的请求继续发送更新的数据?通常,您会收到请求,执行您的操作并回复.完成,连接关闭。但在这种情况下,每次我的应用程序中的事件触发时,我想将 "stick" 发送到响应流并发送新数据。
对我来说,问题在于这种基于事件的方法:它不是一些基于时间间隔的轮询和更新。而是该应用程序像 "Hey, something happend. I really should tell you about it!"
TL;DR: how can I hold on to that response-stream and send updates - not based on loops or timers, but each time certain events fire?
此外,在我忘记之前:我知道,那里有图书馆正在做这件事。但是从我到目前为止所看到的(以及我所理解的;如果我错了请纠正我)这些解决方案取决于 ASP.NET / MVC / 你的名字。由于我只是在编写一个 "plain" C# 应用程序,我认为我不符合这些要求。
这听起来很适合 SignalR。注意 SignalR 是 ASP.NET 系列的一部分,但是这不需要 ASP.NET 框架 (System.Web) 或 IIS,如评论中所述。
澄清一下,SignalR 是 ASP.NET 的一部分。根据他们的网站:
ASP.NET is an open source web framework for building modern web apps
and services with .NET. ASP.NET creates websites based on HTML5, CSS,
and JavaScript that are simple, fast, and can scale to millions of
users.
SignalR 对 System.Web 或 IIS 没有硬依赖。
您可以自行托管 ASP.Net 应用程序(参见 https://docs.microsoft.com/en-us/aspnet/signalr/overview/deployment/tutorial-signalr-self-host)。如果你使用.net core,它实际上默认是自托管的,并作为一个普通的控制台应用程序运行。
至于 light-weight 服务器,我会使用 OWIN selfhost WebAPI (https://docs.microsoft.com/en-us/aspnet/web-api/overview/hosting-aspnet-web-api/use-owin-to-self-host-web-api)。
一个简单的 server-sent 事件服务器操作基本上是这样的:
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 headers,然后保持连接打开以在数据可用时写入数据。
在我的示例中,事件在 extra-task 中生成,该 extra-task 被赋予 producer-consumer collection 并添加事件(此处为每秒的当前时间),如果它们可用collection。每当新事件到达时,GetConsumingEnumerable 都会自动收到通知。然后将新事件以正确的 server-sent 事件格式写入流并刷新。实际上,您需要每分钟左右发送一些 pseudo-ping 事件,因为长时间保持打开状态而没有通过它们发送数据的流将被 OS/framework.[=13= 关闭。 ]
测试这个的示例客户端代码如下:
在异步方法中编写以下代码。
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());
}
}
}
}
对于一个项目,我需要在 C# 应用程序中实现 SSE (Server Sent Events)。虽然这听起来很简单,但我不知道如何解决这个问题。
由于我是 C# 的新手(尽管对一般编程来说并不陌生),我游览了 Google 并试图寻找一些示例代码。根据我目前所见,我可以学习使用 C# 或 consume 服务器发送的事件来构建 HTTP 服务器。但是我没有找到关于发送 SSE 的信息。
我想解决的问题:我怎样才能通过传入的请求继续发送更新的数据?通常,您会收到请求,执行您的操作并回复.完成,连接关闭。但在这种情况下,每次我的应用程序中的事件触发时,我想将 "stick" 发送到响应流并发送新数据。
对我来说,问题在于这种基于事件的方法:它不是一些基于时间间隔的轮询和更新。而是该应用程序像 "Hey, something happend. I really should tell you about it!"
TL;DR: how can I hold on to that response-stream and send updates - not based on loops or timers, but each time certain events fire?
此外,在我忘记之前:我知道,那里有图书馆正在做这件事。但是从我到目前为止所看到的(以及我所理解的;如果我错了请纠正我)这些解决方案取决于 ASP.NET / MVC / 你的名字。由于我只是在编写一个 "plain" C# 应用程序,我认为我不符合这些要求。
这听起来很适合 SignalR。注意 SignalR 是 ASP.NET 系列的一部分,但是这不需要 ASP.NET 框架 (System.Web) 或 IIS,如评论中所述。
澄清一下,SignalR 是 ASP.NET 的一部分。根据他们的网站:
ASP.NET is an open source web framework for building modern web apps and services with .NET. ASP.NET creates websites based on HTML5, CSS, and JavaScript that are simple, fast, and can scale to millions of users.
SignalR 对 System.Web 或 IIS 没有硬依赖。
您可以自行托管 ASP.Net 应用程序(参见 https://docs.microsoft.com/en-us/aspnet/signalr/overview/deployment/tutorial-signalr-self-host)。如果你使用.net core,它实际上默认是自托管的,并作为一个普通的控制台应用程序运行。
至于 light-weight 服务器,我会使用 OWIN selfhost WebAPI (https://docs.microsoft.com/en-us/aspnet/web-api/overview/hosting-aspnet-web-api/use-owin-to-self-host-web-api)。
一个简单的 server-sent 事件服务器操作基本上是这样的:
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 headers,然后保持连接打开以在数据可用时写入数据。
在我的示例中,事件在 extra-task 中生成,该 extra-task 被赋予 producer-consumer collection 并添加事件(此处为每秒的当前时间),如果它们可用collection。每当新事件到达时,GetConsumingEnumerable 都会自动收到通知。然后将新事件以正确的 server-sent 事件格式写入流并刷新。实际上,您需要每分钟左右发送一些 pseudo-ping 事件,因为长时间保持打开状态而没有通过它们发送数据的流将被 OS/framework.[=13= 关闭。 ]
测试这个的示例客户端代码如下:
在异步方法中编写以下代码。
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());
}
}
}
}