SignalR入坑笔记
什么是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服务端和客户端实时通信的例子:
- 客户端发起连接请求
- 服务器端接受请求, 并向该客户端发出计数, 从0到10
- 当计数到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');
}
}
客户端定义了ReceiveUpdate 和 Finished可以让服务端调用的方法.
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入坑笔记的更多相关文章
- react 入坑笔记(四) - React 事件绑定和传参
React 事件处理 建议:在了解 js 的 this 取值后食用更佳. 一.react 与 Html 中用法的异同和注意点 html 中的绑定事件的写法: <button onclick=&q ...
- Linux探索之路1---CentOS入坑笔记整理
前言 上次跟运维去行方安装行内环境,发现linux命令还是不是很熟练.特别是用户权限分配以及vi下的快捷操作.于是决定在本地安装一个CentOS虚拟机,后面有时间就每天学习一点Linux常用命令. 作 ...
- react 入坑笔记(三) - Props
React Props props - 参数. 组件类 React.Component 有个 defaultProps 属性,以 class xxx extend React.Component 形式 ...
- es6 入坑笔记(三)---数组,对象扩展
数组扩展 循环 arr.foreach(){ //回调函数 function(val,index,arr){ //val:当前读取到的数组的值,index:当前读取道德数组的索引,arr:当前的数组名 ...
- es6 入坑笔记(二)---函数扩展,箭头函数,扩展运算符...
函数扩展 1.函数可以有默认值 function demo( a = 10,b ){} 2.函数可以使用解构 function demo( { a = 0,b = 0 } = {} ){ } 3.函数 ...
- es6 入坑笔记(一)---let,const,解构,字符串模板
let 全面取代var 大概相似于C++的定义,一个变量必须得先定义后使用,没有预编译 注意let的作用域,一个{}就是一个作用域,上述规则须在一个作用于内 坑:for(let i =0;i < ...
- 《Scala入坑笔记》缘起 3天就搞了一个 hello world
有小伙伴向我咨询 play framework 的问题,我就想了解一下 play framework ,按照官方的文档,要使用 SBT 安装,就掉进了 SBT 的坑. 第一坑:国外仓库太慢 安装完成后 ...
- react 入坑笔记(六) - 组件的生命周期
React 组件生命周期 详细参考: react 组件生命周期 组件的生命周期可分为三个状态: 1.Mounting:已经挂载/插入到真实 DOM 树上: 2.Updating:正在被重新渲染: 3. ...
- react 入坑笔记(五) - 条件渲染和列表渲染
条件渲染和列表渲染 一.条件渲染 条件渲染较简单,使用 JavaScript 操作符 if 或条件运算符来创建表示当前状态的元素,然后让 React 根据它们来更新 UI. 贴一个小栗子: funct ...
随机推荐
- python实现域名注册查询
author:摘繁华-蓝白社区 联合出品 域名生成与查询 文件说明: [x] .py源文件 [x] .exe可执行文件 [x] .config.json配置文件 ps: .exe和config.jso ...
- Java 中的 equals() 和 hashCode()
equals() 和 hashCode() 在 Object 类中以本地方法的形式存在,Java 中所有的类都继承了 Object 类,因此所有的类中都包含了这两个方法.这两个方法在 Java 开发中 ...
- C语言的类型大小
C语言的类型大小 设计程序的时候我们一般会考虑的尽量的周全,尤其是像C这样的静态类型语言. 有一些溢出的问题就源于没有搞清楚变量的大小范围,所以我们编写的时候需要特别注意 C的整形(整数类型)大小 C ...
- halcon案例学习之cbm_label_simple
*cbm_label_simple 程序说明:*这个示例程序展示了如何使用基于组件的匹配来定位复合对象.在这种情况下,应该在图像中找到一个标签,用户既不知道其中的组件,也不知道它们之间的关系.因此,创 ...
- 大厂面试官竟然这么爱问Kafka,一连八个Kafka问题把我问蒙了?
本文首发于公众号:五分钟学大数据 在面试的时候,发现很多面试官特别爱问Kafka相关的问题,这也不难理解,谁让Kafka是大数据领域中消息队列的唯一王者,单机十万级别的吞吐量,毫秒级别的延迟,这种天生 ...
- 【Spring】Spring 事务控制
Spring 事务控制 Spring 事务控制介绍 JavaEE 体系进行分层开发,事务控制位于业务层,Spring 提供了分层设计业务层的事务处理解决方案. Spring 的事务控制都是基于 AOP ...
- 【Spring】Spring中的Bean - 5、Bean的装配方式(XML、注解(Annotation)、自动装配)
Bean的装配方式 简单记录-Java EE企业级应用开发教程(Spring+Spring MVC+MyBatis)-Spring中的Bean 文章目录 Bean的装配方式 基于XML的装配 基于注解 ...
- ps的参数解释
[root@bogon ~]# ps axuUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND user启动进程的用户 pid 表示进程标志 ...
- 【EXP】比较大的dmp文件导入的时候可以将界面关掉
有一个需求,将一个dmp文件导入到数据库中,但是这个crt的回话有timeout,3分钟,所以一到三分钟就掉线,导入就失败了,这让人很头疼,关闭界面,imp也就终止了 最后想到了几招,分享一下: 1. ...
- python—打开图像文件报错
今天使用python打开一张图像文件的时候报错了 UnicodeDecodeError: 'gbk' codec can't decode byte 0xff in position 0: illeg ...