在.Net MVC开发中,长轮询是实现服务端主动向客户端推送实时数据的常用方案,它基于HTTP协议,通过客户端发起请求后服务端 hold 住连接,直到有数据更新或超时再返回响应的机制,实现类实时的通信效果,相比WebSocket兼容性更好,适合不需要高频双向通信的场景。

长轮询的核心实现原理
长轮询的基本流程分为三步:
- 客户端向服务端发起HTTP请求,设置较长的超时时间
- 服务端接收到请求后,检查是否有待推送的数据,若暂时没有则挂起当前请求,等待数据更新或超时
- 当服务端有数据更新时,立即返回响应给客户端,客户端处理完响应后立即再次发起新的请求,形成循环
.Net MVC服务端实现步骤
1. 定义异步控制器方法
长轮询需要服务端能够挂起请求,因此需要使用异步控制器方法,避免占用线程池资源。首先在控制器中定义异步的轮询接口:
using System.Threading.Tasks;
using System.Web.Mvc;
namespace LongPollingDemo.Controllers
{
public class MessageController : AsyncController
{
// 模拟消息存储,实际项目中可替换为数据库、缓存等存储
private static string _latestMessage = "";
private static object _lockObj = new object();
// 异步轮询入口方法
public void PollAsync(string lastMessageId)
{
// 开启异步操作
AsyncManager.OutstandingOperations.Increment();
// 模拟检查是否有新消息,实际项目中可监听数据变更事件
Task.Run(() =>
{
// 最多等待30秒
int waitTime = 0;
while (waitTime < 30)
{
lock (_lockObj)
{
// 若消息有更新则直接返回
if (!string.IsNullOrEmpty(_latestMessage))
{
AsyncManager.Parameters["message"] = _latestMessage;
AsyncManager.OutstandingOperations.Decrement();
return;
}
}
// 每隔1秒检查一次
System.Threading.Thread.Sleep(1000);
waitTime++;
}
// 超时无新消息,返回空
AsyncManager.Parameters["message"] = "";
AsyncManager.OutstandingOperations.Decrement();
});
}
// 异步操作完成后的回调方法
public ActionResult PollCompleted(string message)
{
return Json(new { hasNew = !string.IsNullOrEmpty(message), data = message }, JsonRequestBehavior.AllowGet);
}
// 模拟服务端更新消息的接口
[HttpPost]
public ActionResult UpdateMessage(string newMessage)
{
lock (_lockObj)
{
_latestMessage = newMessage;
}
return Json(new { success = true });
}
}
}
2. 配置异步请求超时时间
默认的HTTP请求超时时间较短,需要修改Web.config中的请求执行超时配置,确保长轮询的请求不会被提前中断:
<configuration>
<system.web>
<!-- 设置执行超时时间为60秒,适配长轮询的等待需求 -->
<httpRuntime targetFramework="4.8" executionTimeout="60" />
</system.web>
</configuration>
客户端实现逻辑
客户端使用JavaScript发起轮询请求,处理响应后循环发起新请求:
// 记录最后获取到的消息ID,实际项目中可用来做增量校验
let lastMessageId = "";
// 轮询函数
function startPolling() {
fetch(`/Message/PollAsync?lastMessageId=${lastMessageId}`)
.then(response => response.json())
.then(data => {
if (data.hasNew) {
// 处理新消息
console.log("收到新消息:", data.data);
// 更新最后消息ID
lastMessageId = new Date().getTime().toString();
}
// 无论是否有新消息,都立即发起下一次轮询
startPolling();
})
.catch(error => {
console.error("轮询请求失败:", error);
// 失败后延迟1秒重试,避免频繁请求
setTimeout(startPolling, 1000);
});
}
// 页面加载后启动轮询
window.onload = function() {
startPolling();
};
长轮询的注意事项
- 服务端挂起请求时需要使用异步操作,避免占用IIS线程池资源,防止高并发时服务不可用
- 客户端请求超时时间需要和与服务端等待时间匹配,避免客户端提前断开连接
- 若项目需要更高性能的实时通信,可考虑使用SignalR框架,它内部会根据环境自动选择长轮询、WebSocket等最优传输方式
- 长轮询不适合高频数据更新场景,高频场景下连接频繁断开重连会带来额外开销
方案对比
以下是长轮询和其他常见实时通信方案的对比:
| 方案 | 兼容性 | 实时性 | 服务端开销 |
|---|---|---|---|
| 长轮询 | 所有支持HTTP的浏览器 | 中等 | 中等 |
| WebSocket | 现代浏览器,旧浏览器不支持 | 高 | 低 |
| 短轮询 | 所有支持HTTP的浏览器 | 低 | 高 |