1、什么是SignalR

  ASP.NET SignalR 是一个面向 ASP.NET 开发人员的库,可简化向应用程序添加实时 Web 功能的过程。 实时 Web 功能是让服务器代码在可用时立即将内容推送到连接的客户端,而不是让服务器等待客户端请求新数据。

  SignalR使用的三种底层传输技术分别是Web Socket, Server Sent Events 和 Long Polling, 它让你更好的关注业务问题而不是底层传输技术问题。

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

  (现在也很难找到不支持WebSocket的浏览器了,所以我们一般定义必须使用WebSocket)

2、我们做一个聊天室,实现一下SignalR前后端通讯

  由简入深,先简单实现一下 

  2.1 服务端Net5

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Threading.Tasks; namespace ServerSignalR.Models
{
public class ChatRoomHub:Hub
{
public override Task OnConnectedAsync()//连接成功触发
{
return base.OnConnectedAsync();
} public Task SendPublicMsg(string fromUserName,string msg)//给所有client发送消息
{
string connId = this.Context.ConnectionId;
string str = $"[{DateTime.Now}]{connId}\r\n{fromUserName}:{msg}";
return this.Clients.All.SendAsync("ReceivePublicMsg",str);//发送给ReceivePublicMsg方法,这个方法由SignalR机制自动创建
}
}
}

  Startup添加

        static string _myAllowSpecificOrigins = "MyAllowSpecificOrigins";
public void ConfigureServices(IServiceCollection services)
{ services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ServerSignalR", Version = "v1" });
});
services.AddSignalR();
services.AddCors(options =>
{
options.AddPolicy(_myAllowSpecificOrigins, policy =>
{
policy.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.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ServerSignalR v1"));
}
app.UseCors(_myAllowSpecificOrigins);
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<ChatRoomHub>("/Hubs/ChatRoomHub");
});
}

  2.2 前端Angular

    引入包

npm i --save @microsoft/signalr

    ts:

import { Component, OnInit } from '@angular/core';
import * as signalR from '@microsoft/signalr';
import { CookieService } from 'ngx-cookie-service'; @Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
msg = '';
userName='kxy'
public messages: string[] = [];
public hubConnection: signalR.HubConnection; constructor(
private cookie: CookieService
) {this.hubConnection=new signalR.HubConnectionBuilder()
.withUrl('https://localhost:44313/Hubs/ChatRoomHub',
{
skipNegotiation:true,//跳过三个协议协商
transport:signalR.HttpTransportType.WebSockets,//定义使用WebSocket协议通讯
}
)
.withAutomaticReconnect()
.build();
this.hubConnection.on('ReceivePublicMsg',msg=>{
this.messages.push(msg);
console.log(msg);
});
}
ngOnInit(): void {
}
JoinChatRoom(){
this.hubConnection.start()
.catch(res=>{
this.messages.push('连接失败');
throw res;
}).then(x=>{
this.messages.push('连接成功');
});
}
SendMsg(){
if(!this.msg){
return;
}
this.hubConnection.invoke('SendPublicMsg', this.userName,this.msg);
}
}

  这样就简单实现了SignalR通讯!!!

  有一点值得记录一下

    问题:强制启用WebSocket协议,有时候发生错误会被屏蔽,只是提示找不到/连接不成功

    解决:可以先不跳过协商,调试完成后再跳过

3、引入Jwt进行权限验证

安装Nuget包:Microsoft.AspNetCore.Authentication.JwtBearer

  Net5的,注意包版本选择5.x,有对应关系

  Startup定义如下

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using ServerSignalR.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using JwtHelperCore; namespace ServerSignalR
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
static string _myAllowSpecificOrigins = "MyAllowSpecificOrigins";
public void ConfigureServices(IServiceCollection services)
{ services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ServerSignalR", Version = "v1" });
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;//是否需要https
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,//是否验证Issuer
ValidateAudience = false,//是否验证Audience
ValidateLifetime = true,//是否验证失效时间
ValidateIssuerSigningKey = true,//是否验证SecurityKey
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("VertivSecurityKey001")),//拿到SecurityKey
};
options.Events = new JwtBearerEvents()//从url获取token
{
OnMessageReceived = context =>
{
if (context.HttpContext.Request.Path.StartsWithSegments("/Hubs/ChatRoomHub"))//判断访问路径
{
var accessToken = context.Request.Query["access_token"];//从请求路径获取token
if (!string.IsNullOrEmpty(accessToken))
context.Token = accessToken;//将token写入上下文给Jwt中间件验证
}
return Task.CompletedTask;
}
};
}
); services.AddSignalR(); services.AddCors(options =>
{
options.AddPolicy(_myAllowSpecificOrigins, policy =>
{
policy.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.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ServerSignalR v1"));
} app.UseCors(_myAllowSpecificOrigins);
app.UseHttpsRedirection(); app.UseRouting(); //Token 授权、认证
app.UseErrorHandling();//自定义的处理错误信息中间件
app.UseAuthentication();//判断是否登录成功
app.UseAuthorization();//判断是否有访问目标资源的权限 app.UseEndpoints(endpoints =>
{
endpoints.MapHub<ChatRoomHub>("/Hubs/ChatRoomHub");
endpoints.MapControllers();
});
}
}
}

  红色部分为主要关注代码!!!

  因为WebSocket无法自定义header,token信息只能通过url传输,由后端获取并写入到上下文

  认证特性使用方式和http请求一致:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Linq;
using System.Threading.Tasks; namespace ServerSignalR.Models
{
[Authorize]//jwt认证
public class ChatRoomHub:Hub
{ public override Task OnConnectedAsync()//连接成功触发
{
return base.OnConnectedAsync();
} public Task SendPublicMsg(string msg)//给所有client发送消息
{
var roles = this.Context.User.Claims.Where(x => x.Type.Contains("identity/claims/role")).Select(x => x.Value).ToList();//获取角色
var fromUserName = this.Context.User.Identity.Name;//从token获取登录人,而不是传入(前端ts方法的传入参数也需要去掉)
string connId = this.Context.ConnectionId;
string str = $"[{DateTime.Now}]{connId}\r\n{fromUserName}:{msg}";
return this.Clients.All.SendAsync("ReceivePublicMsg",str);//发送给ReceivePublicMsg方法,这个方法由SignalR机制自动创建
}
}
}

  然后ts添加

  constructor(
private cookie: CookieService
) {
var token = this.cookie.get('spm_token');
this.hubConnection=new signalR.HubConnectionBuilder()
.withUrl('https://localhost:44313/Hubs/ChatRoomHub',
{
skipNegotiation:true,//跳过三个协议协商
transport:signalR.HttpTransportType.WebSockets,//定义使用WebSocket协议通讯
accessTokenFactory:()=> token.slice(7,token.length)//会自动添加Bearer头部,我这里已经有Bearer了,所以需要截掉
}
)
.withAutomaticReconnect()
.build();
this.hubConnection.on('ReceivePublicMsg',msg=>{
this.messages.push(msg);
console.log(msg);
});
}

4、私聊

  Hub

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Generic;
using System.Threading.Tasks; namespace ServerSignalR.Models
{
[Authorize]//jwt认证
public class ChatRoomHub:Hub
{
private static List<UserModel> _users = new List<UserModel>();
public override Task OnConnectedAsync()//连接成功触发
{
var userName = this.Context.User.Identity.Name;//从token获取登录人
_users.Add(new UserModel(userName, this.Context.ConnectionId));
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
var userName = this.Context.User.Identity.Name;//从token获取登录人
_users.RemoveRange(_users.FindIndex(x => x.UserName == userName), 1);
return base.OnDisconnectedAsync(exception);
} public Task SendPublicMsg(string msg)//给所有client发送消息
{
var fromUserName = this.Context.User.Identity.Name;
//var ss = this.Context.User!.FindFirst(ClaimTypes.Name)!.Value;
string str = $"[{DateTime.Now}]\r\n{fromUserName}:{msg}";
return this.Clients.All.SendAsync("ReceivePublicMsg",str);//发送给ReceivePublicMsg方法,这个方法由SignalR机制自动创建
} public Task SendPrivateMsg(string destUserName, string msg)
{
var fromUser = _users.Find(x=>x.UserName== this.Context.User.Identity.Name);
var toUser = _users.Find(x=>x.UserName==destUserName);
string str = $"";
if (toUser == null)
{
msg = $"用户{destUserName}不在线";
str = $"[{DateTime.Now}]\r\n系统提示:{msg}";
return this.Clients.Clients(fromUser.WebScoketConnId).SendAsync("ReceivePrivateMsg", str);
}
str = $"[{DateTime.Now}]\r\n{fromUser.UserName}-{destUserName}:{msg}";
return this.Clients.Clients(fromUser.WebScoketConnId,toUser.WebScoketConnId).SendAsync("ReceivePrivateMsg", str);
}
}
}

  TS:

//加一个监听
this.hubConnection.on('ReceivePublicMsg', msg => {
this.messages.push('公屏'+msg);
console.log(msg);
});
this.hubConnection.on('ReceivePrivateMsg',msg=>{
this.messages.push('私聊'+msg);
console.log(msg);
}); //加一个发送
if (this.talkType == 1)
this.hubConnection.invoke('SendPublicMsg', this.msg);
if (this.talkType == 3){
console.log('11111111111111');
this.hubConnection.invoke('SendPrivateMsg',this.toUserName, this.msg);
}

5、在控制器中使用Hub上下文

  Hub链接默认30s超时,正常情况下Hub只会进行通讯,而不再Hub里进行复杂业务运算

  如果涉及复杂业务计算后发送通讯,可以将Hub上下文注入外部控制器,如

namespace ServerSignalR.Controllers
{
//[Authorize]
public class HomeController : Controller
{
private IHubContext<ChatRoomHub> _hubContext;
public HomeController(IHubContext<ChatRoomHub> hubContext)
{
_hubContext = hubContext;
}
[HttpGet("Welcome")]
public async Task<ResultDataModel<bool>> Welcome()
{
await _hubContext.Clients.All.SendAsync("ReceivePublicMsg", "欢迎");
return new ResultDataModel<bool>(true);
}
}
}

  

  至此,感谢关注!!

SignalR WebSocket通讯机制的更多相关文章

  1. 深入浅出ghostbuster剖析NodeJS与PhantomJS的通讯机制

    深入浅出ghostbuster剖析NodeJS与PhantomJS的通讯机制 蔡建良 2013-11-14 一. 让我们开始吧 通过命令行来执行 1) 进行命令窗口: cmd 2) 进入resourc ...

  2. 浅谈HTML5 WebSocket的机制

    回想上一章 在上一章<为什么我们须要HTML5 WebSocket>中,我简单的介绍了下WebSocket的前世今生.相信大家已对WebSocket有了初步的了解.那么今天我们继续深入学习 ...

  3. 【工业串口和网络软件通讯平台(SuperIO)教程】八.SuperIO通讯机制与设备驱动对接的说明

    SuperIO相关资料下载:http://pan.baidu.com/s/1pJ7lZWf 1.1    通讯机制说明 通讯的总体机制采用呼叫应答方式,就是上位机软件主动发送请求数据命令,下位机终端接 ...

  4. 【工业串口和网络软件通讯平台(SuperIO)教程】一.通讯机制

    1.1    应用场景 通讯平台的交互对象包括两方面:第一.与硬件产品交互.第二.与软件产品交互.基本这两方面考虑,通讯平台一般会应用在两个场景: 1)通讯平台应用在PC机上 主要应用在自动站的工控机 ...

  5. ActiveMQ之 TCP通讯机制

    ActiveMQ支持多种通讯协议TCP/UDP等,我们选取最常用的TCP来分析ActiveMQ的通讯机制.首先我们来明确一个概念:  客户(Client):消息的生产者.消费者对ActiveMQ来说都 ...

  6. webSocket通讯

    1.使用facebook第三方SRWebSocket进行websocket通讯. pod 'SocketRocket' 2.通讯地址: ws://192.168.1.128:18882/ws 注意:s ...

  7. 【node+小程序+web端】简单的websocket通讯

    [node+小程序+web端]简单的websocket通讯 websoket是用来做什么的? 聊天室 消息列表 拼多多 即时通讯,推送, 实时交互 websoket是什么 websocket是一个全新 ...

  8. Flask 实现 WebSocket 通讯---群聊和私聊

    一.WebSocket介绍 WebSocket是一种在单个TCP连接实现了服务端和客户端进行双向文本或二进制数据通信的一种通信的协议. WebSocket使得客户端和服务器之间的数据交换变得更加简单, ...

  9. 前端使用express+node实现接口模拟及websocket通讯

    简述如何使用node+express实现接口连接及入门websocket通讯.使用技术栈:node + express + typescript + websocket. 1.接口实现 这里描述前端如 ...

  10. websocket通讯协议(10版本)简介

    前言: 工作中用到了websocket 协议10版本的,英文的协议请看这里: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotoc ...

随机推荐

  1. Java所用相关软件的大致安装流程

    JAVA下载流程 一.相关环境的安装与配置 1.JDK的下载 去官网搜索相应的java版本,并进行下载 官网链接:www.xfdown.com/soft/125774.html在该链接下,可以下载ja ...

  2. Python常见部分内置方法与操作

    Python常见内置方法与操作 整型int 类型转换 int(其它数据类型),但只支持数字类型和小数类型 >>> num1 = input('Your age>>> ...

  3. [MyBatis]问题:ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.

    错误信息 ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging onl ...

  4. 【JSOI2008】最大值

    [JSOI2008]最大值 线段树裸题!动态RMQ. 这道题的操作是直接在序列末尾添加数值,所以连\(push_{down}\),以及建树什么的都不用了.. 这真是写过的最简短的一道\(seg_{tr ...

  5. Cesium 案例(九)示例中小程序集合(1)

    因为这几天在忙一些客观上无法逃脱的事,没有大块时间对中大型案例进行学习,所以对官方案例中的代码不超过40行的程序进行了学习.我把他们放在一到两个随笔中. 注:[所有案例中最前面务必加上] 1 Cesi ...

  6. Linux进程管理(命令)入门

    进程是一个运行中的程序 进程查看 ps 能够查看当前终端下运行的进程 $ ps PID TTY TIME CMD 26305 pts/0 00:00:00 bash 26312 pts/0 00:00 ...

  7. 基础常用API总结2

    String java.lang包下 返回值类型 方法 功能 boolean matches(String regex) 如果匹配当前字符串中regex(正则表达式)所表示的字符,如果有返回ture没 ...

  8. [2]SpinalHDL教程——Scala简单入门

    第一个 Scala 程序 shell里面输入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!&qu ...

  9. 轻量化3D文件格式转换HOOPS Exchange新特性

    BIM与AEC市场发展现状 近年来BIM(建筑信息模型)和AEC(建筑.工程和施工)市场一直保持着持续增长.2014 年全球 BIM 软件市场价值 27.6 亿美元,而到 2022年,预期到达115. ...

  10. Java的final修饰符

    final 实例域 可以将实例域定义为 final.对于 final 域来说,构建对象时必须初始化 final 实例域,构造对象之后就不允许改变 final 实例域的值了.也就是说,必须确保在每一个构 ...