长轮询

我们知道, HTTP 请求发出后,⼀般会给服务器留⼀定的时间做响应,⽐如 3 秒,规定时间内没返回,就 认为是超时。

如果我们的 HTTP 请求将超时设置的很⼤,⽐如 30 秒, 在这 30 秒内只要服务器收到了扫码请求,就⽴⻢ 返回给客户端⽹⻚。如果超时,那就⽴⻢发起下⼀次请求。

这样就减少了 HTTP 请求的个数,并且由于⼤部分情况下,⽤户都会在某个 30 秒的区间内做扫码操作, 所以响应也是及时的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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更简单易实现。