什么是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反向代理

点这里

SignalR入坑笔记的更多相关文章

  1. react 入坑笔记(四) - React 事件绑定和传参

    React 事件处理 建议:在了解 js 的 this 取值后食用更佳. 一.react 与 Html 中用法的异同和注意点 html 中的绑定事件的写法: <button onclick=&q ...

  2. Linux探索之路1---CentOS入坑笔记整理

    前言 上次跟运维去行方安装行内环境,发现linux命令还是不是很熟练.特别是用户权限分配以及vi下的快捷操作.于是决定在本地安装一个CentOS虚拟机,后面有时间就每天学习一点Linux常用命令. 作 ...

  3. react 入坑笔记(三) - Props

    React Props props - 参数. 组件类 React.Component 有个 defaultProps 属性,以 class xxx extend React.Component 形式 ...

  4. es6 入坑笔记(三)---数组,对象扩展

    数组扩展 循环 arr.foreach(){ //回调函数 function(val,index,arr){ //val:当前读取到的数组的值,index:当前读取道德数组的索引,arr:当前的数组名 ...

  5. es6 入坑笔记(二)---函数扩展,箭头函数,扩展运算符...

    函数扩展 1.函数可以有默认值 function demo( a = 10,b ){} 2.函数可以使用解构 function demo( { a = 0,b = 0 } = {} ){ } 3.函数 ...

  6. es6 入坑笔记(一)---let,const,解构,字符串模板

    let  全面取代var 大概相似于C++的定义,一个变量必须得先定义后使用,没有预编译 注意let的作用域,一个{}就是一个作用域,上述规则须在一个作用于内 坑:for(let i =0;i < ...

  7. 《Scala入坑笔记》缘起 3天就搞了一个 hello world

    有小伙伴向我咨询 play framework 的问题,我就想了解一下 play framework ,按照官方的文档,要使用 SBT 安装,就掉进了 SBT 的坑. 第一坑:国外仓库太慢 安装完成后 ...

  8. react 入坑笔记(六) - 组件的生命周期

    React 组件生命周期 详细参考: react 组件生命周期 组件的生命周期可分为三个状态: 1.Mounting:已经挂载/插入到真实 DOM 树上: 2.Updating:正在被重新渲染: 3. ...

  9. react 入坑笔记(五) - 条件渲染和列表渲染

    条件渲染和列表渲染 一.条件渲染 条件渲染较简单,使用 JavaScript 操作符 if 或条件运算符来创建表示当前状态的元素,然后让 React 根据它们来更新 UI. 贴一个小栗子: funct ...

随机推荐

  1. .NET 云原生架构师训练营(模块二 基础巩固 RabbitMQ Masstransit 异常处理)--学习笔记

    2.6.8 RabbitMQ -- Masstransit 异常处理 异常处理 其他 高级功能 异常处理 异常与重试 重试配置 重试条件 重新投递信息 信箱 异常与重试 Exception publi ...

  2. python模块/文件/日期时间

    文件操作:

  3. 与HBase对比,Cassandra的优势特性是什么?

    在1月9日Cassandra中文社区开年活动开始之前的闲聊时间,活动的四位嘉宾就"HBase和Cassandra的对比"这一话题展开了讨论.   总的来说,HBase和Cassan ...

  4. export PATH=$PATH:/usr/local/mysql/bin

    [root@test]# export PATH=$PATH:/usr/local/mysql/bin[root@test]# echo $PATH/usr/local/sbin:/usr/local ...

  5. 【MySQL】ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing

    今天上午遇到了一个问题,新创建的mysql5.7的数据库,由于初始化有点问题,没有给root密码,用了免密码登录. 但是,修改了root密码之后,把配置中的免密登录的配置注释掉后,重启服务.服务正常启 ...

  6. 【Linux】服务器识别ntfs移动磁盘方法

    Linux服务器无法识别ntfs磁盘 如果想识别的话,需要安装一个包ntfs-3g 安装好后,将移动磁盘插入到服务器的usb口中 新建一个目录,将磁盘挂载在新建的目录上 挂载命令如下: mount - ...

  7. HTML5表格详细教程

    HTML5表格 文章目录 HTML5表格 5.1 定义表格 5.1.1 普通表格.列标题 5.1.2 表格标题 5.1.3 表格行分组.表格列分组 5.2 表格属性 5.2.1 单线表格.分离单元格 ...

  8. 基于Redo Log和Undo Log的MySQL崩溃恢复流程

    在之前的文章「简单了解InnoDB底层原理」聊了一下MySQL的Buffer Pool.这里再简单提一嘴,Buffer Pool是MySQL内存结构中十分核心的一个组成,你可以先把它想象成一个黑盒子. ...

  9. Linq.Expressions扩展ExpressionExtension

    手上有一个以前项目用到的.NET工具类封装的DLL. 正好又想试一下动态LAMBDA表达式,用.NET Reflector看一下源码. public static class ExpressionEx ...

  10. 同步与异步 Python 有何不同?

    你是否听到人们说过,异步 Python 代码比"普通(或同步)Python 代码更快?果真是那样吗? 1 "同步"和"异步"是什么意思? Web 应用 ...