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

7 浏览
0 Comments

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

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

由于我对C#还很陌生(虽然对编程并不陌生),所以我去Google上寻找一些示例代码。到目前为止,我发现可以学习如何用C#构建HTTP服务器或者消费服务器发送的事件,但没有找到关于如何发送SSE的信息。

我试图理解的问题是:我如何在接收到请求后继续发送更新的数据?通常,你会接收到一个请求,执行操作然后回复,然后关闭连接。但在这种情况下,我希望能够“粘”在响应流中,并且每当应用程序中的某个事件触发时,发送新的数据。

对我来说,问题在于这种基于事件的方法:它不是基于循环或定时器的轮询和更新。而是应用程序会像这样说:“嘿,发生了什么事情。我真的应该告诉你!”

简而言之:我如何保持对响应流的控制并发送更新数据,而不是基于循环或计时器,而是在特定事件发生时发送?

另外,在我忘记之前:我知道有一些库可以做到这一点。但从我目前看到的(以及我理解的内容;如果我理解有误,请纠正我),这些解决方案都依赖于ASP.NET/MVC/你来命名它。而我只是在编写一个“纯粹”的C#应用程序,我不认为我满足这些要求。

0
0 Comments

问题的出现原因是作者想要在C#中实现发送服务器发送事件,但是不希望使用ASP.NET/MVC等框架。他们已经注意到了SignalR这个库,但是因为SignalR是ASP.NET的一部分,这让他们有些犹豫。虽然SignalR不依赖于System.Web或IIS,但是作者还是希望找到一种在非ASP.NET环境下使用SignalR的方法。

解决方法是使用自托管的ASP.NET应用程序来实现。如果使用的是.NET Core,那么默认情况下就是自托管的,可以作为普通的控制台应用程序运行。还有可能将SignalR包含在VSTO插件中的想法,但是担心这样做会导致线程问题(即服务器阻塞后续代码路径)。

在对SignalR的讨论中,某些情况下SignalR虽然是ASP.NET的一部分,但是并不意味着它依赖于System.Web。作者表示他并没有说SignalR依赖于System.Web,而且ASP.NET Core根本没有任何对System.Web的依赖。他还提到了Web API和SignalR在依赖ASP.NET方面是一样的。最后,讨论的双方对文章进行了一些修改,以便更准确地表达信息。

作者想要在C#中实现发送服务器发送事件,但不使用ASP.NET/MVC等框架。解决方法是使用自托管的ASP.NET应用程序来实现,可以将SignalR包含在VSTO插件中。同时,作者强调SignalR不依赖于System.Web或IIS,特别是ASP.NET Core根本不依赖于System.Web。

0
0 Comments

问题的出现原因是希望在没有使用ASP.NET、MVC等框架的情况下,在C#中实现发送服务器发送事件(Server Sent Events)。解决方法是使用OWIN自托管的WebAPI来实现一个简单的服务器,然后使用PushStreamContent来发送HTTP响应头,并保持连接以在数据可用时写入数据。

以下是示例代码:

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())
                {
                    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 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事件,因为长时间未发送数据的打开流可能会被操作系统/框架关闭。

以下是用于测试的示例客户端代码:

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());
            }
        }
    }
}

这段代码会在一个异步方法中使用。它使用HttpClient来获取流,并使用StreamReader读取数据。然后,在一个无限循环中将数据打印到控制台。

为什么要使用这种方法而不是SignalR呢?因为对于一些特定需求,如在桌面C# WPF应用程序中消费服务器发送事件,可能不能控制客户端的实现。在这种情况下,使用上述方法可以实现流式传输。当然,对于流式传输的其他场景,SignalR是一个很好的工具。

0