我们知道, HTTP 请求发出后,⼀般会给服务器留⼀定的时间做响应,⽐如 3 秒,规定时间内没返回,就 认为是超时。
如果我们的 HTTP 请求将超时设置的很⼤,⽐如 30 秒, 在这 30 秒内只要服务器收到了扫码请求,就⽴⻢ 返回给客户端⽹⻚。如果超时,那就⽴⻢发起下⼀次请求。
这样就减少了 HTTP 请求的个数,并且由于⼤部分情况下,⽤户都会在某个 30 秒的区间内做扫码操作, 所以响应也是及时的。
using Microsoft.AspNetCore.Mvc;
using System.Threading;
[ApiController]
[Route("api/[controller]")]
public class LongPollingController : ControllerBase
{
private static List<string> _messages = new List<string>();
private static int _lastMessageId = 0;
private static object _lockObject = new object();
// 发送消息的端点
[HttpPost("send")]
public IActionResult SendMessage([FromBody] MessageRequest request)
{
lock (_lockObject)
{
_messages.Add(request.Message);
_lastMessageId++;
}
return Ok(new { Status = "Message sent" });
}
// 长轮询接收消息的端点
[HttpGet("receive")]
public async Task<IActionResult> ReceiveMessages(int lastId = -1, int timeoutSeconds = 30)
{
var startTime = DateTime.UtcNow;
var endTime = startTime.AddSeconds(timeoutSeconds);
while (DateTime.UtcNow < endTime)
{
List<string> newMessages;
int currentLastId;
lock (_lockObject)
{
currentLastId = _lastMessageId;
newMessages = _messages.Skip(lastId + 1).ToList();
}
// 如果有新消息,立即返回
if (newMessages.Count > 0)
{
return Ok(new
{
Messages = newMessages,
LastId = currentLastId
});
}
// 等待一小段时间后再检查
await Task.Delay(500);
}
// 超时返回空结果
return Ok(new { Messages = new string[0], LastId = _lastMessageId });
}
}
public class MessageRequest
{
public string Message { get; set; }
}
实现原理说明
服务端:
维护一个消息队列和最后消息ID
接收消息端点用于添加新消息到队列
长轮询端点循环检查是否有新消息,如果有则立即返回,否则等待一段时间继续检查直到超时
客户端:
持续调用长轮询端点获取新消息
记录最后接收到的消息ID,下次请求时传给服务器
收到消息后处理,并准备下一次轮询
这种实现方式相比传统轮询减少了不必要的请求,提高了效率,同时比WebSocket更简单易实现。