وب از ابتدا بر اساس پروتکل HTTP طراحی شد؛ پروتکلی که ذاتاً Request-Response است. یعنی مرورگر (کلاینت) یک درخواست ارسال میکند و سرور تنها یک پاسخ میدهد. این مدل برای بسیاری از کاربردها کافی است، اما برای اپلیکیشنهای Real-Time (مانند چت آنلاین، نوتیفیکیشن لحظهای، معاملات بورسی و …) مناسب نیست.
برای حل این مسئله، طی سالها تکنیکها و فناوریهای مختلفی معرفی شدند که مسیر تکامل Real-Time Web را شکل دادند. در ادامه این روشها را توضیح میدهیم و برای هر کدام نمونه کد در ASP.NET Core و JavaScript/HTML ارائه میکنیم.
AJAX اولین قدم برای ارتباط ناهمگام بین مرورگر و سرور بود. این روش اجازه میدهد بدون رفرش صفحه، درخواست به سرور ارسال و پاسخ دریافت کنیم.
// Controllers/TimeController.cs [ApiController] [Route("api/[controller]")] public class TimeController : ControllerBase { [HttpGet("now")] public IActionResult GetTime() { return Ok(DateTime.Now.ToString("HH:mm:ss")); } }
<!DOCTYPE html> <html> <body> <button ="getTime()">Get Server Time</button> <p id="result"></p> function getTime() { fetch("/api/time/now") .then(res => res.text()) .then(data => { document.getElementById("result").innerText = "Server Time: " + data; }); } </body> </html>
در Polling، کلاینت در بازه های زمانی مشخص (مثلاً هر ۵ ثانیه) به سرور درخواست میزند تا ببیند داده جدید وجود دارد یا نه.
[ApiController] [Route("api/[controller]")] public class MessagesController : ControllerBase { [HttpGet("poll")] public IActionResult Poll() { return Ok("Message: " + DateTime.Now); } }
// هر ۵ ثانیه یکبار درخواست میزنیم setInterval(() => { fetch("/api/messages/poll") .then(res => res.text()) .then(data => console.log("Polling:", data)); }, 5000);
📌 مشکل این روش: حتی وقتی خبری نیست، درخواستهای مکرر باعث بار اضافی روی سرور میشوند.
در Long Polling، کلاینت یک درخواست میزند و سرور آن را نگه میدارد تا زمانی که دادهای جدید برسد یا timeout اتفاق بیفتد. سپس کلاینت بلافاصله یک درخواست جدید باز میکند.
private static string _lastMessage = "Hello"; private static readonly object _lock = new(); [HttpGet("longpoll")] public async Task<IActionResult> LongPoll(CancellationToken cancellationToken) { var currentMessage = _lastMessage; // تا وقتی تغییری در پیام رخ نداده، منتظر میمانیم while (currentMessage == _lastMessage && !cancellationToken.IsCancellationRequested) { await Task.Delay(500, cancellationToken); } return Ok(_lastMessage); } [HttpPost("send")] public IActionResult Send([FromForm] string message) { lock (_lock) { _lastMessage = message; } return Ok("Message updated!"); }
function startLongPolling() { fetch("/api/messages/longpoll") .then(res => res.text()) .then(data => { console.log("Long Polling:", data); startLongPolling(); // دوباره درخواست جدید }) .catch(err => { console.error("Error:", err); setTimeout(startLongPolling, 2000); // تلاش مجدد در صورت خطا }); } startLongPolling();
📌 مزیت: نسبت به Polling ساده، بهینهتر است.
📌 ایراد: همچنان overhead بالایی دارد و نسبت به WebSocket ضعیفتر است.
SSE یکی از ویژگیهای HTML5 است که اجازه میدهد سرور دادهها را به صورت یکطرفه به کلاینت ارسال کند.
[ApiController] [Route("api/[controller]")] public class SseController : ControllerBase { [HttpGet("stream")] public async Task Stream(CancellationToken cancellationToken) { Response.Headers.Add("Content-Type", "text/event-stream"); while (!cancellationToken.IsCancellationRequested) { var message = $"data: Server time is {DateTime.Now}\n\n"; var bytes = System.Text.Encoding.UTF8.GetBytes(message); await Response.Body.WriteAsync(bytes, 0, bytes.Length, cancellationToken); await Response.Body.FlushAsync(cancellationToken); await Task.Delay(2000, cancellationToken); } } }
<!DOCTYPE html> <html> <body> <h3>Server-Sent Events Example</h3> <ul id="messages"></ul> const evtSource = new EventSource("/api/sse/stream"); evtSource.onmessage = (event) => { const li = document.createElement("li"); li.textContent = event.data; document.getElementById("messages")(li); }; </body> </html>
📌 محدودیت: یکطرفه است (فقط سرور → کلاینت)، و فقط داده متنی ارسال میشود.
WebSocket یک پروتکل جدید در لایه ۷ است که ارتباط دوطرفه و پایدار بین کلاینت و سرور برقرار میکند.
// Program.cs var app = WebApplication.CreateBuilder(args).Build(); app.UseWebSockets(); app.Map("/ws", async context => { if (context.WebSockets.IsWebSocketRequest) { var webSocket = await context.WebSockets.AcceptWebSocketAsync(); var buffer = new byte[1024 * 4]; while (true) { var result = await webSocket.ReceiveAsync( new ArraySegment<byte>(buffer), CancellationToken.None); if (result.MessageType == WebSocketMessageType.Close) { await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); break; } var msg = Encoding.UTF8.GetString(buffer, 0, result.Count); var response = Encoding.UTF8.GetBytes("Echo: " + msg); await webSocket.SendAsync( new ArraySegment<byte>(response), WebSocketMessageType.Text, true, CancellationToken.None); } } }); app.Run();
<!DOCTYPE html> <html> <body> <h3>WebSocket Example</h3> <input id="msg" type="text" placeholder="Type message..." /> <button ="sendMessage()">Send</button> <ul id="chat"></ul> const socket = new WebSocket("ws://localhost:5000/ws"); socket.onmessage = (event) => { const li = document.createElement("li"); li.textContent = event.data; document.getElementById("chat")(li); }; function sendMessage() { const msg = document.getElementById("msg").value; socket.send(msg); } </body> </html>
📌 مزیت: سریعترین و بهینهترین روش برای Real-Time.
SignalR کتابخانه مایکروسافت است که ارتباط Real-Time را ساده میکند. SignalR به صورت خودکار بین WebSocket, SSE, Long Polling سوییچ میکند.
public class ChatHub : Hub { public async Task SendMessage(string user, string message) { await Clients.All.SendAsync("ReceiveMessage", user, message); } }
var builder = WebApplication.CreateBuilder(args); builder.Services.AddSignalR(); var app = builder.Build(); app.MapHub<ChatHub>("/chatHub"); app.Run();
<!DOCTYPE html> <html> <head> <script > </head> <body> <h3>SignalR Chat Example</h3> <input id="user" placeholder="User" /> <input id="msg" placeholder="Message" /> <button ="sendMessage()">Send</button> <ul id="chat"></ul> const connection = new signalR.HubConnectionBuilder() .withUrl("/chatHub") .build(); connection.on("ReceiveMessage", (user, message) => { const li = document.createElement("li"); li.textContent = `${user}: ${message}`; document.getElementById("chat")(li); }); connection.start().then(() => { console.log("Connected to SignalR"); }); function sendMessage() { const user = document.getElementById("user").value; const msg = document.getElementById("msg").value; connection.invoke("SendMessage", user, msg); } </body> </html>
AJAX → شروع ارتباط ناهمگام (Request-Response).
Polling → پرسوجوی دورهای، ساده اما پرهزینه.
Long Polling → نگه داشتن درخواست تا رسیدن داده جدید.
SSE → ارتباط یکطرفه سرور → کلاینت، فقط متنی.
WebSocket → ارتباط دوطرفه و پایدار.
SignalR → لایه انتزاعی در .NET که بهترین روش را انتخاب میکند.