シンプルな長い接続デモ
リアルタイム応答の基本ハンドラ実装例:
public class SimpleHandler : IHttpHandler
{
public void ProcessRequest(HttpContext ctx)
{
ctx.Response.ContentType = "text/plain";
ctx.Response.Write("接続成功");
}
public bool IsReusable => false;
}
長時間接続を実現する非同期ハンドラ:
public class AsyncPollingHandler : IHttpAsyncHandler
{
public IAsyncResult BeginProcessRequest(HttpContext ctx, AsyncCallback callback, object state)
{
var asyncOp = new PollingOperation(ctx, callback, state);
Task.Run(async () =>
{
await Task.Delay(5000);
ctx.Response.Write("ポーリング応答");
asyncOp.Complete();
});
return asyncOp;
}
public void EndProcessRequest(IAsyncResult ar) => (ar as PollingOperation)?.Dispose();
public void ProcessRequest(HttpContext ctx) { }
public bool IsReusable => false;
}
public class PollingOperation : IAsyncResult
{
private readonly ManualResetEvent _syncLock;
public PollingOperation(HttpContext ctx, AsyncCallback cb, object data)
{
Context = ctx;
Callback = cb;
_syncLock = new ManualResetEvent(false);
}
public HttpContext Context { get; }
public AsyncCallback Callback { get; }
public void Complete()
{
IsCompleted = true;
_syncLock.Set();
Callback?.Invoke(this);
}
public void Dispose() => _syncLock.Dispose();
public bool IsCompleted { get; private set; }
public WaitHandle AsyncWaitHandle => _syncLock;
public object AsyncState => null;
public bool CompletedSynchronously => false;
}
セッション管理システム
クライアント接続管理クラス:
public class ClientSession
{
public ClientSession(PollingOperation op)
{
Created = DateTime.Now;
SessionID = Guid.NewGuid().ToString("N")[..8];
Operation = op;
}
public DateTime Created { get; }
public string SessionID { get; }
public PollingOperation Operation { get; set; }
public bool IsCompleted => Operation?.IsCompleted ?? false;
public void Send(string msg)
{
if (IsCompleted) return;
Operation.Context.Response.Write(msg);
Operation.Complete();
}
}
セッション管理クラス:
public class SessionPool
{
private static readonly List<ClientSession> _sessions = new();
private static readonly object _lock = new();
public static void Add(ClientSession session)
{
lock (_lock) { _sessions.Add(session); }
}
public static void Remove(ClientSession session)
{
lock (_lock) { _sessions.Remove(session); }
}
public static ClientSession Find(string id)
{
lock (_lock) { return _sessions.FirstOrDefault(s => s.SessionID == id); }
}
public static void Broadcast(string msg)
{
lock (_lock)
{
foreach (var session in _sessions)
session.Send(msg);
}
}
public static void CleanExpired()
{
lock (_lock)
{
var expired = _sessions
.Where(s => s.Created.AddMinutes(5) < DateTime.Now || s.IsCompleted)
.ToList();
foreach (var session in expired)
_sessions.Remove(session);
}
}
}
統合実装例
接続初期化ハンドラ:
public class InitHandler : IHttpHandler
{
public void ProcessRequest(HttpContext ctx)
{
ctx.Response.Cache.SetCacheability(HttpCacheability.NoCache);
var session = new ClientSession(null);
SessionPool.Add(session);
ctx.Response.Write(session.SessionID);
}
public bool IsReusable => false;
}
非同期ポーリングハンドラ改良版:
public class EnhancedPollingHandler : IHttpAsyncHandler
{
public IAsyncResult BeginProcessRequest(HttpContext ctx, AsyncCallback cb, object data)
{
var sessionId = ctx.Request["sessionid"];
var asyncOp = new PollingOperation(ctx, cb, data);
if (string.IsNullOrEmpty(sessionId))
{
ctx.Response.StatusCode = 400;
asyncOp.Send("セッションIDが必要");
return asyncOp;
}
var session = SessionPool.Find(sessionId);
session.Operation = asyncOp;
return asyncOp;
}
// その他のメソッドは前出と同じ
}
バックグラウンド処理
定期的なブロードキャスト実装:
public class Global : HttpApplication
{
protected void Application_Start()
{
Task.Run(async () =>
{
while (true)
{
await Task.Delay(180000);
SessionPool.Broadcast("サーバー通知: 3分経過");
SessionPool.CleanExpired();
}
});
}
}
クライアントサイド実装
<script>
function startPolling(sessionId) {
$.post("/EnhancedPollingHandler.ashx", { sessionid: sessionId }, function(res) {
$("#output").text(res);
startPolling(sessionId);
});
}
$("#connect").click(function() {
$.post("/InitHandler.ashx", function(sessionId) {
$("#sessionId").val(sessionId);
startPolling(sessionId);
});
});
</script>
<button id="connect">接続</button>
<input id="sessionId" readonly>
<div id="output"></div>