什么是SignalR

ASP.NET Core SignalR 是一个开源库,它简化了向应用程序添加实时 web 功能的功能。 实时 Web 功能使服务器端代码能够即时将内容推送到客户端。

ASP.NET Core SignalR 的一些功能:

  • 自动管理连接
  • 同时向所有连接的客户端发送消息。 例如,聊天室
  • 向特定客户端或客户端组发送消息
  • 可缩放以处理不断增加的流量

SignalR 支持以下用于处理实时通信的技术:

  • WebSockets
  • 服务器发送的事件
  • 长轮询

其中Web Socket仅支持比较现代的浏览器, Web服务器也不能太老.

Server Sent Events 情况可能好一点, 但是也存在同样的问题.

所以SignalR采用了回落机制, SignalR有能力去协商支持的传输类型.

Web Socket是最好的最有效的传输方式, 如果浏览器或Web服务器不支持它的话, 就会降级使用SSE, 实在不行就用Long Polling.

一旦建立连接, SignalR就会开始发送keep alive消息, 来检查连接是否还正常. 如果有问题, 就会抛出异常.

因为SignalR是抽象于三种传输方式的上层, 所以无论底层采用的哪种方式, SignalR的用法都是一样的.

SignalR - 集线器(Hub)

SignalR中, 我们主要需要做的事情就是继承Hub类, 来和客户端互相发送消息; 可以察觉出, SignalR服务器是一个消息中心, 客户端将消息发送给SignalR服务器, 然后有我们来处理这些消息, 可以将这些消息广播出去, 也可以将某个客户端发过来的消息转发给另一个客户端, 实现两个客户端之间的通信;

开始使用SignalR - CountHub

这里将完成一个简单的使用SignalR服务端和客户端实时通信的例子:

  1. 客户端发起连接请求
  2. 服务器端接受请求, 并向该客户端发出计数, 从0到10
  3. 当计数到10, 服务端调用客户端Finished方法, 客户端Finished关闭连接

服务端:

建立asp.net core项目, 选择空模板

新建 CountService 类 和 CountHub

public class CountService
{
private int _count; public int GetLastestCount() => _count++;
}
public class CountHub : Hub
{
private readonly CountService _countService; public CountHub(CountService countService,
ILoggerFactory loggerFactory)
{
_countService=countService;
} public async Task GetLastestCount()
{
IClientProxy client = Clients.Caller;
int count;
do
{
count = _countService.GetLastestCount(); await Task.Delay(1000); await client.SendAsync("ReceiveUpdate", $"ConnectionId: {Context.ConnectionId}, count: {count}");
} while (count < 10); await client.SendAsync("Finished");
}
}

在Startup类中注册Service和配置Hub路由

services.AddScoped<CountService>();

services.AddSignalR();

endpoints.MapHub<CountHub>("/countHub");

Startup
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SingleRStudy.Hubs;
using SingleRStudy.Services; namespace SingleR
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<CountService>(); services.AddSignalR(); services.AddCors(options =>
{
options.AddPolicy("NgClientPolicy", p =>
{
p.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("NgClientPolicy"); app.UseRouting(); app.UseEndpoints(endpoints =>
{
endpoints.MapHub<CountHub>("/countHub");
});
}
}
}

由于我这边使用了Angular来作为客户端, 所以在Startup中同时配置了跨域.

客户端(Angular)

通过npm引入signalr: npm i @microsoft/signalr @types/node

导入signalr: import * as signalR from '@microsoft/signalr/'

完整的如下:

ChatComponent
import { Component, OnInit } from '@angular/core';
import * as signalR from '@microsoft/signalr/' /**
* 创建连接
*/
const connection = new signalR.HubConnectionBuilder()
.withUrl('//localhost:5000/countHub')
.build(); @Component({
selector: 'app-chat',
templateUrl: './chat.component.html',
styleUrls: ['./chat.component.css']
})
export class ChatComponent implements OnInit { constructor() { } async ngOnInit() {
connection.on('ReceiveUpdate', (message: string) => {
console.log(message);
}); connection.on('Finished', () => {
console.log('count finished');
connection.stop();
}); connection.onclose(error => {
console.error('signalR connection closed. error: ', error);
}); // 开始通信
await connection.start().catch(error => {
console.error(error);
});
if(connection.state === signalR.HubConnectionState.Connected)
await connection.send('GetLastestCount', 'aaa');
} }

客户端定义了ReceiveUpdateFinished可以让服务端调用的方法.

ReceiveUpdate方法, 将方法参数在控制台打印出来;

Finished方法则用来关闭连接.

运行结果:

开始使用SignalR - ChatRoom

服务端:

建立 ChatHub

ChatHub
using Microsoft.AspNetCore.SignalR;

namespace SignalRStudy.Hubs
{
public class ChatHub : Hub
{
public async void SendMessage(string username, string message)
{
await Clients.All.SendAsync("ReceiveMessage", username, message);
}
}
}

Startup中要配置一下:

app.UseEndpoints(endpoints =>
{
...
endpoints.MapHub<ChatHub>("/chat");
});

服务端很简单, 就是把收到的消息转发给所有连接着的客户端

客户端(Angular):

ChatService

ChatService
import { Injectable, EventEmitter } from '@angular/core';
import * as signalr from '@microsoft/signalr'
import { Observable, of, Subscribable, Subscriber } from 'rxjs'; const connection = new signalr.HubConnectionBuilder()
.withUrl('http://localhost:5000/chat')
.build(); @Injectable()
export class ChatService {
receivedMessage$ = new Observable<any>(observer => {
connection.on('ReceiveMessage', (username: string, message: string) => {
observer.next({
username,
message
});
});
});
username: string = '匿名用户'; constructor() {
// connection.on('ReceiveMessage', this.receiveMessage);
} async startChat() {
await connection.start();
} async sendMessage(message: string) {
// 等待连接或断开连接操作完成
while(connection.state === signalr.HubConnectionState.Connecting
|| connection.state === signalr.HubConnectionState.Disconnecting);
// 如果未连接服务器, 则尝试进行连接
if(connection.state === signalr.HubConnectionState.Disconnected) {
await connection.start().catch(err => console.error('signalr failed to connect to server.'));
}
if(connection.state === signalr.HubConnectionState.Connected) {
await connection.send('SendMessage', this.username, message);
}
}
}

ChatService中处理了SignalR交互的逻辑, 组件可以通过订阅ReceivedMessage$来获取最新的消息...

下面放一下相关组件的代码:

chat.component.ts
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
import { ChatService } from '../../services/chat.service'; @Component({
selector: 'app-chat',
templateUrl: './chat.component.html',
styleUrls: ['./chat.component.css']
})
export class ChatComponent implements OnInit {
messageToSend: string = '';
receivedMessages: any[] = [];
@ViewChild('messageBox', { static: true }) messageBox:ElementRef; constructor(
private chatServ: ChatService
) {
} async ngOnInit() {
this.chatServ.username = 'laggage'; await this.chatServ.startChat();
this.chatServ.receivedMessage$.subscribe(r => {
this.receivedMessages.push(r);
// 滚动条滑动到最底部, 等待5ms是为了等待angular渲染完界面, 否则可能无法滚动到底部
setTimeout(() => {
let ele = this.messageBox.nativeElement as HTMLDivElement;
ele.scrollTop = ele.scrollHeight;
}, 5);
});
} get username() {
return this.chatServ.username;
} set username(value: string) {
if(value != this.chatServ.username)
this.chatServ.username = value;
} sendMessage() {
this.chatServ.sendMessage(this.messageToSend);
this.messageToSend = '';
}
}
chat.component.html
<div id="wrapper">
<!-- chat works -->
<div id="message-receive-area">
<div id="message-container"
#messageBox
class="px-3 py-2 overflow-auto">
<div class="message-item jumbotron p-0 px-3 py-2 mb-3"
*ngFor="let message of receivedMessages">
<span> {{message.username}}说: </span>
<span class="d-block"> {{message.message}} </span>
</div>
</div>
</div> <div id="message-send-area"
class="container-fluid mx-0 row">
<div id="write-message"
class="col col-8 pl-0">
<textarea name="message"
class="h-100 w-100"
[(ngModel)]="messageToSend"></textarea>
</div>
<div class="col col-4 overflow-hidden pr-0">
<div class="mb-3">
<label for="">
用户名
<input type="text"
class="w-100"
name="username"
placeholder="用户名"
[(ngModel)]="username">
</label>
</div>
<div class="w-100"> <button class="w-100 overflow-hidden"
(click)="sendMessage()">Send</button> </div>
</div>
</div>
</div>
chat.component.css
#message-receive-area {
height: 60vh;
padding: .6rem;
} #message-container {
border: 1px solid black;
height: 100%;
} #message-send-area {
height: 40vh;
padding: .6rem;
}

先这样...

结合了signalr的web api如何使用nginx反向代理

点这里

最新文章

  1. PHP--Warning: Invalid argument supplied for foreach() in ...
  2. Python3中的新特性(1)——新的语言特性
  3. java包和jar包
  4. 安装mono和jexus,运行asp.net程序
  5. [html5] 学习笔记-Canvas 绘制渐变图形与绘制变形图形
  6. python进阶学习(二)
  7. Spring Cloud微服务实践之路-起始
  8. java对文件的基本操作
  9. Python使用Plotly绘图工具,绘制气泡图
  10. 关于redis分布式锁实现原理
  11. 关联tomcat源代码
  12. PL/SQL链接Oracle出现乱码
  13. peewee基本操作
  14. _quest_mod
  15. FTP上传心得
  16. 【JVM.12】线程安全与锁优化
  17. HDU 5242 Game(贪心)
  18. 初步了解hg19注释文件的内容 | gtf
  19. 《Python黑帽子:黑客与渗透测试编程之道》 Windows系统提权
  20. [转]iOS证书(.p12)和描述文件(.mobileprovision)申请

热门文章

  1. volatile 关键字精讲
  2. 【Flutter】功能型组件之异步UI更新
  3. git的使用命令总结
  4. redis 5.0.5 安装
  5. paramunittest参数化测试基础
  6. os.walk() 遍历目录下的文件夹和文件
  7. Redis中哈希分布不均匀该怎么办
  8. SAP 修改表和表中数据
  9. ABAP关键字和ABAP词汇
  10. Ubuntu20.04 安装火狐开发者版本(水狐)步骤