SignalR(二):Server to all Client推播 & Client to all Client廣播

SignalR是一個自己蠻有興趣的東西,提供Server與Client端之間的即時通訊,而且使用也不算複雜。

SignalR所提供的功能有:

  • 自動處理連接管理。
  • 同時將訊息傳送給所有已連線的用戶端。 例如,聊天室。
  • 將訊息傳送給特定用戶端或用戶端群組。
  • 調整以處理增加的流量。

這次利用SignalR來做個訊息推播(Server to Client) & 廣播(Client to Client)的簡單範例。

1.建立SignalR Hub

using Microsoft.AspNetCore.SignalR;

namespace SignalR_Test
{
    public class MyHub: Hub
    {
        public async Task BraodCast(string data)
        {
            await Clients.All.SendAsync("HubMethodBroadcast", data);
        }    
    }
}

 

2.Program.cs註冊SignalR相關服務& 注入

using SignalR_Test;

var builder = WebApplication.CreateBuilder(args);

//CORS設定
builder.Services.AddCors(options =>
{
    options.AddPolicy("CorsPolicy", builder => builder
        .WithOrigins("http://localhost:4200")
        .AllowAnyMethod()
        .AllowAnyHeader()
        .AllowCredentials());
});

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

//註冊相關服務
builder.Services.AddSignalR();
builder.Services.AddSingleton<TimerManager>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseCors("CorsPolicy");

//設定SignalR Hub路由路徑
app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.MapHub<MyHub>("/myHub");
});

app.UseAuthorization();
app.MapControllers();
app.Run();

 

2.建立時間模組(TimerManager),設定推播間隔秒數以及推播時間要多長

namespace SignalR_Test
{
    public class TimerManager
    {
        private Timer? _timer;
        private AutoResetEvent? _autoResetEvent;
        private Action? _action;
        public DateTime TimerStarted { get; set; }
        public bool IsTimerStarted { get; set; }
        public void PrepareTimer(Action action)
        {
            _action = action;
            _autoResetEvent = new AutoResetEvent(false);
            _timer = new Timer(Execute, _autoResetEvent, 1000, 5000);//1秒後開始執行,後續5秒執行推播一次
            TimerStarted = DateTime.Now;
            IsTimerStarted = true;
        }
        public void Execute(object? stateInfo)
        {
            _action();

            //連線60秒後,中斷SignalR(Server to Client)的推播
            if ((DateTime.Now - TimerStarted).TotalSeconds > 60)
            {
                IsTimerStarted = false;
                _timer.Dispose();
            }
        }
    }
}

 

3.建立API Controller Method

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;

namespace SignalR_Test.Controllers
{
    [ApiController]
    [Route("api/[controller]/[action]")]

    public class SignalRController : ControllerBase
    {
        private readonly IHubContext<MyHub> _hub;
        private readonly TimerManager _timer;

        public SignalRController(IHubContext<MyHub> hub, TimerManager timer)
        {
            _hub = hub;
            _timer = timer;
        }

        public IActionResult Subscribe()
        {
            if (!_timer.IsTimerStarted)
            {//是否在連線時限60秒內,是的話就調用Hub Method [HubMethodGetRandomNumber]
                _timer.PrepareTimer(() => _hub.Clients.All.SendAsync("HubMethodGetRandomNumber", GetRandom()));
            }
                
            return Ok(new { Message = "Request Completed" });
        }

        public int GetRandom() 
        { 
            var rnd = new Random();
            var Res = rnd.Next(0, 100);
            return Res;
        }
    }
}

1.安裝 @microsoft/signalr

npm install @microsoft/signalr

 

2.建立SignalR Service

import { Injectable } from '@angular/core';
import * as signalR from '@microsoft/signalr'
import { environment } from 'src/environments/environment';

@Injectable({
  providedIn: 'root'
})

export class SignalrService {
  public broadcastMsg: string = '';
  hubConn: signalR.HubConnection | undefined
  url: string = `${environment.apiBaseUrl}`;

  constructor() { }

  //建立SignalR 連線   
  public startConnection = () => {

    //建立SignalR Hub 連線,withUrl的參數要跟
    this.hubConn = new signalR.HubConnectionBuilder().withUrl(`${this.url}/myHub`).build();

    //開始連線
    this.hubConn.start()
    .then(() => {
      console.log('signalr connection start');
    })
    .catch(
      err => {
        console.log(err);
      }
    )
  }

  //監聽Hub Method [HubMethodGetRandomNumber]並取得結果
  public addMethodListener = () => {   
    this.hubConn?.on('HubMethodGetRandomNumber', (data) => {
      console.log(data);
    })
  }

  //透過調用server端MyHub的BraodCast方法,來調用Hub Method [HubMethodBroadcast]
  public broadcastMessage(msg: string): void{
    this.hubConn?.invoke('BraodCast', msg)
    .catch(err => {
      console.error(err);
    })
  }

  //監聽Hub Method [HubMethodBroadcast]並取得結果
  public broadcastMethodListener(): void{
    this.hubConn?.on('HubMethodBroadcast', (data) => {
      this.broadcastMsg = data;
      console.log(data);
    })
  }
}

this.hubConn = new signalR.HubConnectionBuilder().withUrl(`${this.url}/myHub`).build();的參數必須要跟後端的SignalR Hub路由路徑一致(大小寫沒有差),以本篇文章的範例來說,SignalR Hub的完整路徑為[https://localhost:7193/myHub]

his.hubConn?.on('HubMethodGetRandomNumber', (data) => {……})HubMethodGetRandomNumber這個參數,跟後端Subscribe()Method的 _hub.Clients.All.SendAsync("HubMethodGetRandomNumber", GetData())

的第一個參數的HubMethodGetRandomNumber要一樣,指的是前端要監聽的SignalR Hub Method的名稱,而GetRandom則是實際要執行的callback function。

 

3.頁面實際應用

import { HttpClient, HttpResponse } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { SignalrService } from 'src/app/core/services/signalr.service';
import { environment } from 'src/environments/environment';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {

  constructor(
    private _signalrService: SignalrService,
    private _http: HttpClient
    ) { }

  ngOnInit(): void 
  {
    //建立SignalR 連線    
    this._signalrService.startConnection();

    //監聽Hub Method [HubMethodGetRandomNumber]並取得結果
    this._signalrService.addMethodListener();

    //透過呼叫api,調用Hub Method [HubMethodGetRandomNumber],並取得結果
    this._http.get<any>(`${environment.apiBaseUrl}/api/SignalR/Subscribe`).subscribe(res => {       
      console.log(res.message);
    })

    //監聽Hub Method [HubMethodGetRandomNumber]並取得結果
    this._signalrService.broadcastMethodListener();
  }

  //發送廣播訊息
  broadcastMessage(): void{
    this._signalrService.broadcastMessage("1111");
  }
}

4.應用頁面HTML,因應訊息廣播,增加一個Button來發送廣播訊息

<p>home works!</p>

<button (click)="broadcastMessage();">Broadcast.</button>

  • 實際測試結果(Server to all Client)

可以看到分別開兩個不同的瀏覽器頁面,但所接收到的推播訊息都是一樣的。

  • 實際測試結果(Client to all Client),按下Broadcast後,兩個瀏覽器的畫面都會分別收到1111的廣播訊息。

Ref:
1.NET Core with SignalR and Angular – Real-Time Charts
2.https://ithelp.ithome.com.tw/articles/10251470