Client端使用Angular 10 來實作SignalR
Server端則使用Windows Service來實作
目的:
client會發送一組sid通知server
server會針對有該組sid的connid做持續廣播
Server端
1.
透過nuget安裝
Microsoft.AspNet.SignalR.SelfHost
Microsoft.Owin.Cors
Microsoft.Owin.Cors
2. 新增Startup.cs
using Microsoft.AspNet.SignalR;
using Microsoft.Owin.Cors;
using Owin;
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration()
{
EnableDetailedErrors = true,
EnableJSONP = true
};
map.RunSignalR(hubConfiguration);
});
}
}
3. 新增BaseHub.cs
using Microsoft.AspNet.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WindowsServiceSample.Signalr
{
public class BaseHub : Hub
{
/// <summary>
/// 紀錄目前已連結的 Client 資料[key = ConnID, valie = sID]
/// </summary>
public static Dictionary<string, string> CurrClients = new Dictionary<string, string>();
/// <summary>
/// 複寫OnConnected
/// </summary>
/// <returns></returns>
public override Task OnConnected()
{
return base.OnConnected();
}
/// <summary>
/// 複寫OnReconnected
/// </summary>
/// <returns></returns>
public override Task OnReconnected()
{
return base.OnReconnected();
}
/// <summary>
/// 斷線
/// </summary>
/// <returns></returns>
public override Task OnDisconnected(bool stop)
{
Remove(Context.ConnectionId);
return base.OnDisconnected(stop);
}
/// <summary>
/// 斷線時移除該使用者
/// </summary>
/// <param name="connectionId"></param>
private void Remove(string connectionId)
{
lock (CurrClients)
{
foreach (var item in CurrClients.ToArray())
{
if (item.Key == connectionId)
{
CurrClients.Remove(item.Key);
}
}
}
}
}
}
4. 新增BroadcastHub.cs
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WindowsServiceSample.Signalr
{
[HubName("broadcastHub")]
public class BroadcastHub : BaseHub
{
public class Para
{
public string sID { set; get; }
}
public void Register(Para para)
{
CurrClients.Add(Context.ConnectionId, para.sID);
}
}
}
5. 修改Service1.cs
/// <summary>
/// 服務啟動
/// </summary>
protected override void OnStart(string[] args)
{
//初始化SignalR
InitializeSelfHosting();
}
/// <summary>
/// 初始化SignalR
/// </summary>
public void InitializeSelfHosting()
{
const string url = "http://localhost:8080";
WebApp.Start(url);
_Timer = new Timer(1000) { AutoReset = true };
_Timer.Elapsed += Timer_Elapsed;
_Timer.Start();
}
private static void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
var signalR = GlobalHost.ConnectionManager.GetHubContext<Signalr.BroadcastHub>();
//發送系統時間給client
signalR.Clients.All.systemTime(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"));
foreach (var item in Signalr.BroadcastHub.CurrClients)
{
//發送訊息給特定sID的client
switch (item.Value)
{
case "1001":
signalR.Clients.Client(item.Key).broadcast("sID = 1001, Good!..." + new Random().Next(1, 10));
break;
case "1002":
signalR.Clients.Client(item.Key).broadcast("sID = 1002, Good!..." + new Random().Next(11, 20));
break;
default:
signalR.Clients.Client(item.Key).broadcast("sID = null, Error!..." + new Random().Next(-10, 0));
break;
}
}
}
Client端 (當Server端是採用Microsoft.AspNet.SignalR)
1. 安裝 ng2-signalr
npm install ng2-signalr jquery signalr --save
2. 新增signal-r-test component
ng g c /page/signal-r-test
3. 新增app.resolve.ts
import { Resolve } from '@angular/router';
import { SignalR, ISignalRConnection } from 'ng2-signalr';
import { Injectable } from '@angular/core';
@Injectable()
export class SignalRResolver implements Resolve<ISignalRConnection > {
constructor(public signalR: SignalR) { }
resolve() {
return this.signalR.connect();
}
}
4. 修改app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { SignalRTestComponent } from './page/signal-r-test/signal-r-test.component';
import { environment } from 'src/environments/environment';
import { SignalRResolver } from './app.resolve';
import { SignalRModule } from 'ng2-signalr';
import { SignalRConfiguration } from 'ng2-signalr';
export function createConfig(): SignalRConfiguration {
const c = new SignalRConfiguration();
c.hubName = 'broadcastHub';
c.qs = {};
c.url = environment.APIWebSite; // 'http://localhost:8080/'
c.logging = false;
// >= v5.0.0
c.executeEventsInZone = true; // optional, default is true
c.executeErrorsInZone = false; // optional, default is false
c.executeStatusChangeInZone = true; // optional, default is true
return c;
}
@NgModule({
declarations: [
AppComponent,
SignalRTestComponent
],
imports: [
BrowserModule,
AppRoutingModule,
SignalRModule.forRoot(createConfig),
],
providers: [SignalRResolver],
bootstrap: [AppComponent]
})
export class AppModule { }
5. 修改app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { SignalRResolver } from './app.resolve';
import { SignalRTestComponent } from './page/signal-r-test/signal-r-test.component';
const routes: Routes = [
{ path: 'SignalR/:sid', component: SignalRTestComponent, resolve: { connection: SignalRResolver } },
];
@NgModule({
imports: [RouterModule.forRoot(routes, { useHash: true, scrollPositionRestoration: 'enabled' })],
exports: [RouterModule]
})
export class AppRoutingModule { }
6. 修改signal-r-test.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { timer } from 'rxjs';
import { BroadcastEventListener, SignalRConnection, ConnectionStatus } from 'ng2-signalr';
@Component({
selector: 'app-signal-r-test',
templateUrl: './signal-r-test.component.html',
styleUrls: ['./signal-r-test.component.css']
})
export class SignalRTestComponent implements OnInit {
connection: SignalRConnection;
sID: string;
systemTime: string;
broadcast: string;
constructor(private activatedRoute: ActivatedRoute) {
this.connection = activatedRoute.snapshot.data['connection'];
}
ngOnInit(): void {
this.activatedRoute.paramMap.subscribe((params) => {
this.sID = params.get('sid').toString();
this.StartSignalR();
});
}
StartSignalR(): void {
// Socket斷線5秒後自動重連
this.connection.errors.subscribe(err => {
console.log('Error while establishing connection... Retrying...');
timer(5000).subscribe(() => {
this.connection.start();
this.connection.status.subscribe(x => {
if (x.value === 1) {
console.log('Reconnect...Work!');
this.ConnectiSignalR();
}
});
});
});
this.ConnectiSignalR();
}
// 連接SingalR
ConnectiSignalR(): void {
// 1.create a listener object
const onSystemTime = new BroadcastEventListener<any>('systemTime');
const onBroadcast = new BroadcastEventListener<any>('broadcast');
// 2.register the listener
this.connection.listen(onSystemTime);
this.connection.listen(onBroadcast);
// 3.subscribe for incoming messages
onSystemTime.subscribe((res: any) => {
this.systemTime = res;
});
onBroadcast.subscribe((res: any) => {
this.broadcast = res;
});
const data = {
sID: this.sID,
};
this.connection.invoke('Register', data).catch(err => {
console.log('Error:' + err);
});
}
}
7. 修改signal-r-test.component.html
<p>signal-r-test works!</p>
<span>SystemTime:{{systemTime}}</span>
<br>
<span>Broadcast:{{broadcast}}</span>
8. 執行結果
如果Server為.net core版本的SignalR
請參考此篇
請參考此篇
參考
- SignalR 2 Server with Console Application ver
- [C#]ConsoleApplication 環境下使用 SignalR
- 教學課程:SignalR 自我裝載
- ASP.NET SignalR Hubs API Guide - Server (C#)
- .NET Core with SignalR and Angular – Real-Time Charts