6.12长轮询 Long Polling

长轮询(Long Polling)是一种实现服务器向客户端推送数据的技术。它通过保持 HTTP 连接打开直到有新数据发送给客户端,或者直到超时为止,从而模拟了服务器推送的效果。长轮询是传统轮询(即客户端定期向服务器发送请求以检查是否有新的数据)的一种改进,可以减少不必要的请求,提高效率。

长轮询 Long Polling#

长轮询(Long Polling)是一种实现服务器向客户端推送数据的技术。它通过保持 HTTP 连接打开直到有新数据发送给客户端,或者直到超时为止,从而模拟了服务器推送的效果。长轮询是传统轮询(即客户端定期向服务器发送请求以检查是否有新的数据)的一种改进,可以减少不必要的请求,提高效率。

长轮询的工作原理:

  1. 客户端向服务器发起一个请求。
  2. 如果服务器上没有新数据,服务器不会立即响应这个请求,而是将请求挂起。
  3. 一旦服务器上有新数据可供发送,或达到了预设的超时时间,服务器就会响应请求,并发送数据给客户端。
  4. 客户端处理完数据后,再次向服务器发起一个新的请求,重复上述过程。

长轮询的应用场景:

  • 实时通知:例如,在线聊天应用中,当用户收到新消息时,服务器可以通过长轮询及时推送消息给客户端。
  • 在线协作工具:如多人同时编辑文档的应用,长轮询可以用来实时同步用户的编辑操作。
  • 游戏更新:在网络游戏中,长轮询可用于实时更新游戏状态,比如玩家位置、得分等信息。
  • 股票市场更新:金融应用程序中使用长轮询来实时显示股票价格变动。
  • 配置中心:在微服务架构中,配置中心使用长轮询技术来确保各个服务能够即时接收到最新的配置变更。当配置发生更改时,配置中心可以迅速将更新推送到所有相关的服务实例,确保配置的一致性和时效性。

以下示例展示了如何使用长轮询请求:

cs
await httpRemoteService.LongPollingAsync("https://localhost:7044/HttpRemote/LongPolling"    , async (responseMessage, token) =>    {        Console.WriteLine(await responseMessage.Content.ReadAsStringAsync(token));        await Task.CompletedTask;    }, cancellationToken: cancellationToken);// 使用构建器模式await httpRemoteService.SendAsync(HttpRequestBuilder    .LongPolling("https://localhost:7044/HttpRemote/LongPolling"    , async (responseMessage, token) =>    {        Console.WriteLine(await responseMessage.Content.ReadAsStringAsync(token));        await Task.CompletedTask;    }), cancellationToken: cancellationToken);

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

cs
await foreach (var responseMessage in httpRemoteService.LongPollingAsAsyncEnumerable("https://localhost:7044/HttpRemote/LongPolling", cancellationToken: cancellationToken)){    // 注意:每个响应在使用后需要手动释放(或使用 using)    using (responseMessage)    {        Console.WriteLine(await responseMessage.Content.ReadAsStringAsync(cancellationToken));    }}// 使用构建器模式await foreach (var responseMessage in httpRemoteService.SendAsAsyncEnumerable(HttpRequestBuilder.LongPolling("https://localhost:7044/HttpRemote/LongPolling"), cancellationToken)){    using (responseMessage)    {        Console.WriteLine(await responseMessage.Content.ReadAsStringAsync(cancellationToken));    }}

虽然长轮询在一定程度上解决了实时通信的需求,但它也有一些缺点,比如在高并发情况下可能会对服务器造成较大压力,以及长时间的连接可能会影响服务器的性能。随着 Web 技术的发展,Server-Sent EventsWebSocket 等更先进的技术逐渐成为实现实时双向通信的首选方案。然而,在某些受限环境中,长轮询仍然是一个可行的选择。

HttpLongPollingBuilder 构建器#

HttpLongPollingBuilder 构建器是框架提供专门用来发送长轮询请求所需的各项设置。HttpLongPollingBuilder 的构造函数是私有的,因此无法直接使用 new 关键字进行实例化,不过,框架提供了 HttpRequestBuilder.LongPolling 的多个静态重载方法创建 HttpLongPollingBuilder 的实例。

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

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

cs
// 默认为 GET 请求HttpRequestBuilder.LongPolling("https://localhost:7044/HttpRemote/LongPolling"    , async (responseMessage, token) =>    {        Console.WriteLine(await responseMessage.Content.ReadAsStringAsync(cancellationToken));        await Task.CompletedTask;    })    // 设置轮询重试间隔,默认为 2 秒    .SetRetryInterval(TimeSpan.FromSeconds(2))    // 设置最大重试次数,默认为 100 次    .SetMaxRetries(500)    // 设置在接收服务器返回 200~299 状态码的数据的操作    // .SetOnDataReceived(async responseMessage => {})  // 可通过初始时传入    // 设置在接收服务器返回【非】 200~299 状态码的数据的操作    .SetOnError(async responseMessage => {})    // 设置在响应标头包含 X-End-Of-Stream 时触发的操作    .SetOnEndOfStream(async ResponseMessage => {})    // 设置长轮询事件处理程序    .SetEventHandler<CustomLongPollingEventHandler>()    .SetEventHandler(typeof(CustomLongPollingEventHandler))    // 设置 HttpRequestBuilder 实例    .With(builder => {}));  // 支持更多扩展

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

cs
httpRemoteService.Send(httpLongPollingBuilder, cancellationToken);await httpRemoteService.SendAsync(httpLongPollingBuilder, cancellationToken);await foreach (var response in httpRemoteService.SendAsAsyncEnumerable(httpLongPollingBuilder, cancellationToken)){    // 处理每个响应}

长轮询事件处理程序#

IHttpLongPollingEventHandler 接口允许您定义发送长轮询请求的预处理操作。通过实现该接口,您可以创建自定义的长轮询事件处理程序,例如 CustomLongPollingEventHandler 类:

cs
public class CustomLongPollingEventHandler : IHttpLongPollingEventHandler{    // 用于接收服务器返回 200~299 状态码的数据的操作    public Task OnDataReceivedAsync(HttpResponseMessage httpResponseMessage, CancellationToken cancellationToken) {}    // 用于接收服务器返回【非】 200~299 状态码的数据的操作    public Task OnErrorAsync(HttpResponseMessage httpResponseMessage, CancellationToken cancellationToken) {}    // 用于响应标头包含 X-End-Of-Stream 时触发的操作    public Task OnEndOfStreamAsync(HttpResponseMessage httpResponseMessage, CancellationToken cancellationToken) {}}

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

cs
services.TryAddSingleton<CustomLongPollingEventHandler>();

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

cs
HttpRequestBuilder.LongPolling("https://localhost:7044/HttpRemote/LongPolling"    , async (responseMessage, token) =>    {        Console.WriteLine(await responseMessage.Content.ReadAsStringAsync(cancellationToken));        await Task.CompletedTask;    })    .SetEventHandler<CustomLongPollingEventHandler>();HttpRequestBuilder.LongPolling("https://localhost:7044/HttpRemote/LongPolling"    , async (responseMessage, token) =>    {        Console.WriteLine(await responseMessage.Content.ReadAsStringAsync(cancellationToken));        await Task.CompletedTask;    })    .SetEventHandler(typeof(CustomLongPollingEventHandler));    // 使用类型的方式

终止长轮询请求#

除了使用 CancellationToken 来取消长轮询请求,框架还会检查响应标头中的 X-End-Of-Stream,若存在该标头,则终止长轮询请求。