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. 【LeetCode回溯算法#08】递增子序列,巩固回溯算法中的去重问题

    递增子序列 力扣题目链接(opens new window) 给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2. 示例 1: 输入:nums = [4,6,7,7] ...

  2. 微软NewBing真是Niubility

    这是本人2012年的拙作:           晨兮,闻风雨,后而雷鸣电闪.迟不可再三,若故无食.然何如耶?雨大风狂,单车奈何?公交卡空,恐时不予我也.不免叹也,天亦不予我!         而后出, ...

  3. Android笔记--常用布局

    线性布局--LinearLayout 线性布局的方向 orientation属性值:若为horizontal,内部视图在水平方向从左往右排列 若为vertical,内部视图在垂直方向从上往下排列 如果 ...

  4. 干货来袭!3天0基础Python实战项目快速学会人工智能必学数学基础全套(含源码)(第3天)概率分析篇:条件概率、全概率与贝叶斯公式

    第1天:线性代数篇:矩阵.向量.实战编程 第2天:微积分篇:极限与导数.梯度下降.积分.实战编程 第3天:概率分析篇:条件概率与全概率.贝叶斯公式.实战项目 目录 前言 一.概率与机器学习 1.1 概 ...

  5. VUE3.x之Proxy 我们为什么要使用Proxy

    Object.defineProperty 劫持数据 只是对对象的属性进行劫持 无法监听新增属性和删除属性 需要使用 vue.set, vue.delete 深层对象的劫持需要一次性递归 劫持数组时需 ...

  6. JavaScript中计时器requestAnimationFrame、setTimeout、setInterval、setImmediate的使用和区别

    在JavaScript中,我们经常使用requestAnimationFrame.setTimeout.setInterval和setImmediate来控制代码的执行时机.它们各有特点和适用场景: ...

  7. 网络计划技术——关键路线法(Python)

    关键路径法是基于进度网络模型的方法,用网络图表示各项活动之间的相互关系,获得在一定工期.成本.资源约束条件下的最优进度安排.关键路径法源于美国杜邦公司对于项目管理控制成本.减少工期的研究.1959年, ...

  8. pysimplegui之运行多个窗口

    运行多个窗口 这就是 PySimpleGUI 继续简单的地方,但问题空间刚刚进入"复杂"领域. 如果您希望在事件循环中运行多个窗口,那么有两种方法可以做到这一点. 当第二个窗口可见 ...

  9. win32api中文在线文档

    中文文档http://www.yfvb.com/help/win32sdk/ 英文手册https://www.jb51.net/books/724576.html

  10. 常用模块time模块

    时间模块: 一:time import time time的解析: 时间分为三种格式: 第一种: 第二种: 第三种: 二:datatime import datatime  #表达形式 print(d ...