首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

ASP.NET 使用者線上狀態追蹤:解決瀏覽器關閉無法觸發 Session_End 的方案

     編輯:ASP.NET 基礎

核心難題

HTTP 無狀態,使用者關閉瀏覽器不會發送任何請求至伺服器,導致 Session_End(InProc 模式)或分散式 Session 清理無法即時執行,線上名單殘留「鬼魂使用者」。

常見解法比較

方案原理優點缺點
定時 AJAX 心跳前端 setInterval 定期呼叫後端 API 更新最後活動時間即時、可精確判斷離線增加伺服器負載、頻寬
SignalR / WebSocket長連線,連線斷開即觸發離線邏輯真即時、雙向、低延遲需支援 WebSocket 基礎設施、較複雜
Session 超時掃描背景工作定期掃描 Session 最後存取時間,超時標記離線無前端改動、相容性佳延遲 = 掃描間隔 + Session Timeout
beforeunload / unload 事件送 Beacon頁面卸載時發送 navigator.sendBeacon 通知伺服器輕量、不阻塞卸載瀏覽器支援度、可能被攔截

原文提供的 AJAX 心跳實作(改良版)

前端 JavaScript

let heartbeatTimer = null;
const HEARTBEAT_INTERVAL = 30000; // 30 秒
const MAX_MISSED = 2;             // 容許遺漏次數

function startHeartbeat() {
    let missed = 0;
    function tick() {
        fetch('/api/heartbeat', { method: 'POST', keepalive: true })
            .then(r => { if (r.ok) missed = 0; else missed++; })
            .catch(() => missed++);

        if (missed < MAX_MISSED) {
            heartbeatTimer = setTimeout(tick, HEARTBEAT_INTERVAL);
        } else {
            // 連續失敗,視為離線(伺服器端同步清理)
            console.log('Heartbeat stopped');
        }
    }
    tick();
}

// 頁面載入啟動
document.addEventListener('DOMContentLoaded', startHeartbeat);

// 可選:頁面可見性變更時重置
document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'visible') startHeartbeat();
});

後端 API (ASP.NET Core / Web API)

[HttpPost("api/heartbeat")]
public IActionResult Heartbeat() {
    var userId = User.Identity.Name; // 或 Session["UserId"]
    if (string.IsNullOrEmpty(userId)) return Unauthorized();

    // 更新 Redis / 資料庫 / MemoryCache 中的 LastActiveTime
    _cache.Set($"user:{userId}:lastActive", DateTimeOffset.UtcNow, TimeSpan.FromMinutes(30));
    return Ok();
}

離線清理背景服務

public class OnlineUserCleanupService : BackgroundService {
    protected override async Task ExecuteAsync(CancellationToken ct) {
        while (!ct.IsCancellationRequested) {
            var cutoff = DateTimeOffset.UtcNow.AddMinutes(-35); // 比 Session Timeout 稍長
            var staleKeys = _cache.Keys("user:*:lastActive").Where(k => _cache.Get(k) < cutoff);
            foreach (var key in staleKeys) {
                var userId = key.Split(':')[1];
                RemoveFromOnlineList(userId); // 你的清理邏輯
                _cache.Remove(key);
            }
            await Task.Delay(TimeSpan.FromMinutes(5), ct);
        }
    }
}

Session 設定建議

<sessionState mode="InProc" timeout="30" />  

<sessionState mode="SQLServer" timeout="30" sqlConnectionString="..." />
  • timeout 單位為分鐘,建議大於心跳間隔的 2~3 倍。
  • InProc 模式下 Session_End 只有在 IIS 回收或 Session 真正過期才觸發,不可靠作為即時離線依據。

進階:結合 SignalR 實現真即時

// Hub
public class PresenceHub : Hub {
    public override async Task OnConnectedAsync() {
        await OnlineTracker.UserConnected(Context.UserIdentifier, Context.ConnectionId);
        await base.OnConnectedAsync();
    }
    public override async Task OnDisconnectedAsync(Exception e) {
        await OnlineTracker.UserDisconnected(Context.UserIdentifier, Context.ConnectionId);
        await base.OnDisconnectedAsync(e);
    }
}

// 前端
const connection = new signalR.HubConnectionBuilder().withUrl("/presenceHub").build();
connection.start();

SignalR 內建重連、心跳、斷線偵測,是現代 ASP.NET Core 專案的首選方案。

總結建議

  1. 新專案優先採用 SignalR,架構乾淨且即時。
  2. 舊專案(Web Forms / MVC 5)可用 AJAX 心跳 + 快取掃描 低成本改良。
  3. 務必在 伺服器端 以「最後活動時間」為準判斷離線,不要信任前端通知。
  4. 線上名單資料結構建議用 ConcurrentDictionary、Redis Hash 或資料表,支援多伺服器擴展。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved