عنوان:

‫رویکردی نوین به ارتباطات بی‌درنگ: رویدادهای ارسالی سرور (Server-Sent Events) در NET 10.


نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۳/۱۰ ۰۷:۳۷
آدرس: www.dntips.ir
ارتباطات بی‌درنگ (Real-time communications) ستون فقرات بسیاری از برنامه‌های کاربردی مدرن، از به‌روزرسانی‌های زنده سهام گرفته تا اعلان‌های فوری، هستند. در گذشته، توسعه‌دهندگان اغلب به تکنیک‌هایی مانند نظرسنجی (Polling) متوسل می‌شدند که می‌توانست ناکارآمد باشد و منجر به تاخیر در دریافت اطلاعات شود. با این حال، با ظهور رویدادهای ارسالی سرور (Server-Sent Events - SSE)، راهکاری ساده‌تر و کارآمدتر برای جریان داده یک‌طرفه از سرور به کلاینت فراهم شده است. این مقاله به بررسی عمیق SSE، نحوه عملکرد آن، موارد استفاده، و پیاده‌سازی بومی آن در .NET 10 می‌پردازد، و با یک مثال کاربردی، سادگی و قدرت این فناوری را به نمایش می‌گذارد.

آشنایی با رویدادهای ارسالی سرور (SSE)
رویدادهای ارسالی سرور، یک فناوری ارتباطی یک‌طرفه هستند که به سرور امکان می‌دهند تا به‌روزرسانی‌های بی‌درنگ را از طریق پروتکل HTTP به کلاینت ارسال کند. این فناوری بخشی از مشخصات HTML5 است و توسط اکثر مرورگرهای مدرن پشتیبانی می‌شود. برخلاف ارتباطات سنتی درخواست/پاسخ HTTP که در آن کلاینت باید برای دریافت به‌روزرسانی‌ها به طور مداوم سرور را نظرسنجی کند، SSE یک اتصال پایدار (Persistent connection) را فراهم می‌کند که در آن به‌روزرسانی‌ها به طور خودکار از سرور به کلاینت جریان می‌یابند.

نحوه عملکرد SSE
فرآیند کار SSE نسبتاً ساده و مستقیم است:
  • آغاز اتصال توسط کلاینت: کلاینت (معمولاً یک مرورگر یا برنامه فرانت‌اند) با استفاده از EventSource API یک درخواست HTTP به سرور آغاز می‌کند. این API به طور خاص برای مدیریت اتصالات SSE طراحی شده است.
  • باز نگه داشتن اتصال توسط سرور: سرور اتصال را باز نگه می‌دارد و به طور مداوم پیام‌ها را در قالب text/event-stream به کلاینت ارسال می‌کند. این فرمت استاندارد برای رویدادهای ارسالی سرور است که تضمین می‌کند کلاینت می‌تواند پیام‌ها را به درستی تفسیر کند.
  • دریافت و پردازش پیام توسط کلاینت: کلاینت پیام‌ها را به صورت بی‌درنگ دریافت می‌کند و با استفاده از شنونده‌های رویداد (JavaScript event listeners) آن‌ها را پردازش می‌کند. این شنونده‌ها می‌توانند به رویدادهای خاصی گوش دهند و با دریافت داده‌های جدید، رابط کاربری را به‌روزرسانی کنند یا اقدامات دیگری را انجام دهند.

موارد استفاده و محدودیت‌های SSE
SSE در سناریوهای مختلفی که نیاز به به‌روزرسانی‌های بی‌درنگ یک‌طرفه از سرور به کلاینت وجود دارد، بسیار مفید است. برخی از موارد استفاده رایج عبارتند از:
  • اعلان‌های زنده (Live Notifications): مانند اعلان‌های شبکه‌های اجتماعی یا سیستم‌های پیام‌رسان.
  • نشان‌دهنده‌های سهام (Stock Tickers): برای نمایش تغییرات قیمت سهام به صورت لحظه‌ای.
  • گزارش‌های بی‌درنگ (Realtime Logs): نمایش لاگ‌های سیستم به محض تولید.
  • به‌روزرسانی‌های آب و هوای بی‌درنگ (Realtime Weather Updates): ارائه اطلاعات آب و هوا به صورت لحظه‌ای.
با این حال، مهم است که بدانیم SSE برای همه سناریوها مناسب نیست. مواردی که نباید از SSE استفاده کرد شامل:
  • ارتباطات دوطرفه (Bi-directional communications): در سناریوهایی مانند برنامه‌های چت که کلاینت نیز نیاز به ارسال داده به سرور دارد، SSE راه حل مناسبی نیست. در این موارد، فناوری‌هایی مانند WebSockets گزینه بهتری هستند.
  • فرکانس بالای به‌روزرسانی‌های دوطرفه: در کاربردهایی که حجم بالایی از تبادل داده دوطرفه وجود دارد، WebSockets کارایی بیشتری خواهند داشت.

پشتیبانی بومی از SSE در NET 10.
یکی از پیشرفت‌های قابل توجه در NET 10.، پشتیبانی بومی از رویدادهای ارسالی سرور در ASP.NET Core SDK است. ASP.NET Core اکنون از بازگرداندن یک نتیجه ServerSentEvents با استفاده از API جدید TypedResults.ServerSentEvents پشتیبانی می‌کند. این ویژگی هم در Minimal APIs و هم در Controller-based APIs قابل استفاده است که انعطاف‌پذیری زیادی را برای توسعه‌دهندگان فراهم می‌آورد. در .NET، پیام‌های رویداد به عنوان اشیاء SseItem نمایش داده می‌شوند که می‌توانند شامل یک نوع رویداد (event type)، یک شناسه (ID)، و یک بار داده (data payload) از نوع T باشند.

پیاده‌سازی عملی: برنامه شبیه‌سازی اشتراک سفر
برای درک بهتر نحوه کار SSE در .NET 10، یک برنامه شبیه‌سازی اشتراک سفر (Ride Sharing App) را پیاده‌سازی می‌کنیم. هدف این برنامه نمایش به‌روزرسانی‌های بی‌درنگ وضعیت سفر، شامل جهت حرکت و فاصله تا مقصد بعدی، به کلاینت است.

پیاده‌سازی سمت بک‌اند (Backend)
برای سمت بک‌اند، دو سرویس اصلی و یک API Minimal را پیاده‌سازی خواهیم کرد.

سرویس وضعیت سفر (RideStatusService)
این سرویس مسئول نگهداری و اطلاع‌رسانی در مورد تغییرات وضعیت سفر است. این سرویس رابط INotifyPropertyChanged را پیاده‌سازی می‌کند. این رابط استاندارد در .NET برای اطلاع‌رسانی تغییرات در خصوصیات یک شیء است و برای اتصال داده (Data Binding) در WPF و Blazor نیز استفاده می‌شود. با پیاده‌سازی این رابط، هر زمان که خاصیت NextDirection تغییر کند، رویداد PropertyChanged فراخوانی می‌شود و کلاینت‌ها (در این مورد، API) از این تغییر مطلع می‌شوند.
public class RideStatusService: INotifyPropertyChanged
{
    public RideStatusService()
    {
        NextDirection = "Starting the trip";
    }

    private static List<string> _directions = ["left", "right", "straight"];
    private static List<string> _distances = ["50", "60", "70", "80", "90", "100"];
    public event PropertyChangedEventHandler? PropertyChanged;
    
    private string _nextDirection;
    
    private string NextDirection
    {
        get => _nextDirection;
        set
        {
            _nextDirection = value;
            OnPropertyChanged();
        }
    }

    protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public void SetDirection()
    {
        NextDirection = $"Going {_directions[Random.Shared.Next(_directions.Count)]} for {_distances[Random.Shared.Next(_distances.Count)]} meters";
    }
    
    public async IAsyncEnumerable<string> GetNextDirection([EnumeratorCancellation] CancellationToken ct)
    {
        while (ct is not { IsCancellationRequested: true })
        {
            yield return NextDirection;
            var tcs = new TaskCompletionSource();
            PropertyChangedEventHandler handler = (_, _) => tcs.SetResult();
            PropertyChanged += handler;
            try
            {
                await tcs.Task.WaitAsync(ct);
            }
            finally
            {
                PropertyChanged -= handler;
            }
        }
    }
}
متد SetDirection به صورت تصادفی جهت و فاصله جدید را تولید و مقدار NextDirection را به‌روزرسانی می‌کند. متد GetNextDirection یک جریان نامحدود از به‌روزرسانی‌ها را فراهم می‌کند. این متد یک IAsyncEnumerable را برمی‌گرداند که اجازه می‌دهد تا هر زمان که NextDirection تغییر کرد، یک آیتم جدید به جریان اضافه شود. TaskCompletionSource و PropertyChangedEventHandler با هم کار می‌کنند تا هر زمان که تغییری رخ داد، جریان را پیش ببرند.

سرویس پس‌زمینه انتشار وضعیت سفر (RideShareBackgroundWorker)
این سرویس یک سرویس پس‌زمینه (Background Service) است که به طور مداوم و در بازه‌های زمانی مشخص، وضعیت سفر را به‌روزرسانی می‌کند. این سرویس RideStatusService را تزریق کرده و متد SetDirection آن را در یک حلقه نامحدود فراخوانی می‌کند.
public class RideShareBackgroundWorker(RideStatusService rideStatusService): BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            rideStatusService.SetDirection();
            await Task.Delay(1000, stoppingToken);
        }
    }
}

API ارسال رویداد SSE
API Minimal برای ارسال جریان رویداد SSE به صورت زیر تعریف می‌شود:
app.MapGet("/ride-status", (RideStatusService rideStatusService, CancellationToken cancellationToken) => 
    TypedResults.ServerSentEvents(rideStatusService.GetNextDirection(cancellationToken), eventType: "ride-status")
);
این کد یک نقطه پایانی HTTP GET در مسیر /ride-status تعریف می‌کند. وقتی کلاینت به این مسیر متصل می‌شود، TypedResults.ServerSentEvents از جریان GetNextDirection سرویس rideStatusService برای ارسال داده‌ها استفاده می‌کند. نوع رویداد eventType روی "ride-status" تنظیم شده است که به کلاینت اجازه می‌دهد رویدادهای خاص مربوط به وضعیت سفر را فیلتر کند.

ثبت سرویس‌ها در تزریق وابستگی (Dependency Injection)
برای اینکه سرویس‌ها در دسترس برنامه قرار گیرند، باید در سیستم تزریق وابستگی ASP.NET Core ثبت شوند:
builder.Services.AddSingleton<RideStatusService>();
builder.Services.AddHostedService<RideShareBackgroundWorker>();
RideStatusService به عنوان یک نمونه سینگلتون (Singleton) ثبت می‌شود تا اطمینان حاصل شود که یک نمونه واحد از آن در سراسر برنامه وجود دارد. RideShareBackgroundWorker نیز به عنوان یک سرویس هاست شده (Hosted Service) ثبت می‌شود تا ASP.NET Core آن را به عنوان یک سرویس پس‌زمینه مدیریت کند.

پیاده‌سازی سمت فرانت‌اند (Frontend)
برای سمت فرانت‌اند، از JavaScript ساده برای باز کردن اتصال EventSource و گوش دادن به رویدادها استفاده می‌کنیم. HTML شامل یک عنصر با id="status" است که برای نمایش به‌روزرسانی‌ها استفاده می‌شود.
<div class="text-center">
    <h1 class="display-4">Welcome to My Ride Share</h1>
    <h4>You current ride's routing status is:</h4>
    
    <ul id="status"></ul>
</div>

<script>
    const eventSource = new EventSource('http://localhost:5252/ride-status');
    const statusList = document.getElementById('status');

    eventSource.addEventListener('ride-status', event => {
        const li = document.createElement('li');
        li.textContent = event.data;
        statusList.appendChild(li);
    });

    eventSource.onerror = error => {
        console.error('EventSource failed for ride status:', error);
        eventSource.close();
    };
</script>
کد JavaScript یک نمونه جدید از EventSource را ایجاد می‌کند و آن را به نقطه پایانی /ride-status در سرور (با فرض اینکه سرور روی پورت 5252 اجرا می‌شود) متصل می‌کند. یک شنونده رویداد به "ride-status" اضافه می‌شود؛ هر زمان که یک رویداد از نوع "ride-status" دریافت شود، داده‌های آن (event.data) به عنوان یک عنصر لیست به statusList اضافه می‌شود. یک شنونده برای خطاها نیز وجود دارد که در صورت بروز مشکل، خطا را در کنسول گزارش می‌دهد و اتصال EventSource را می‌بندد.

نتیجه‌گیری
با پیاده‌سازی‌های اخیر در .NET 10، استفاده از رویدادهای ارسالی سرور (SSE) به طور قابل توجهی ساده‌تر شده است. این فناوری راهکاری قدرتمند و کارآمد برای سناریوهای ارتباطی یک‌طرفه بی‌درنگ از سرور به کلاینت ارائه می‌دهد. با بهره‌گیری از TypedResults.ServerSentEvents و ترکیب آن با الگوهای طراحی مناسب مانند INotifyPropertyChanged و سرویس‌های پس‌زمینه، توسعه‌دهندگان .NET می‌توانند به راحتی برنامه‌هایی با قابلیت‌های بی‌درنگ قدرتمند بسازند. سادگی پیاده‌سازی در مقایسه با سایر روش‌های ارتباطی بی‌درنگ، SSE را به گزینه‌ای جذاب برای برنامه‌هایی که نیاز به به‌روزرسانی‌های مداوم و بدون نیاز به تعامل دوطرفه دارند، تبدیل می‌کند. این پیشرفت‌ها، توسعه برنامه‌های کاربردی مدرن و واکنش‌گرا را در اکوسیستم .NET بیش از پیش تسهیل می‌کند.


مشاهده مطلب اصلی